개요

Intellij로 Maven 기반의 Java 어플리케이션을 생성한 후 기본적인 설정을 마치고 실행해보니 `Error:java: error: release version 5 not supported` 에러가 발생했다.

요즘같은 시대에 무슨 5 버전인가 하여 Java 의 버전 문제일 것이라고는 생각도 못해서 고려를 안해보았는데 구글링해보니 비슷한 이슈가 많은 듯 하여 정리해본다.


해결방법

지금까지 Gradle만 사용해봐서 해당 이슈를 경험한 적이 없었던 것이었다. Intellij에서 maven 프로젝트를 생성하면 기본적으로 Java 1.5 버전으로 설정한다. 그래서 프로젝트의 컴파일과 관련된 버전들을 모두 추가로 세팅해주어야 한다.  

 

  • Preference > Build, Execution, Deployment > Compiler > Java Compiler 메뉴에 들어가 해당 모듈의 타겟 버전을 default 인 1.5버전을 클릭해 원하는 버전으로 변경해준다.  
  • File > Project Structure > Language level 을 기본 세팅되어 있는 5를 클릭해 원하는 버전으로 변경해준다.  

-끗-

반응형

'Dev > Java' 카테고리의 다른 글

MacOS Java11 버전 업그레이드  (0) 2021.02.27
[Java] 용어정리.zip  (0) 2020.09.29
[Java] Stateless Object  (0) 2020.04.11

개요

현재 본인의 Mac에 설치된 Java 버전은 8이다.

1
2
3
4
$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
cs

Java11이 LTS로 나온지 한참이 지났는데도 미뤄온 버전 업그레이드를 진행한다.

방법

MacOS에서 Java 버전을 업데이트 하기 위해서는 크게 두가지 방법이 있다.
Jdk 파일을 다운로드 받거나, MacOS용 패키지 관리자인 HomeBrew로 설치하는 방법이다.

Oracle에서 제공하는 OpenJdk는 현재 HomeBrew로 설치를 지원하지 않는다.
Oracle의 OpenJdk를 설치하려면 15버전 기준으로 jdk.java.net/15/ 링크에서 파일 다운로드가 가능하다.
새로운 Jdk 버전이 6개월에 한번씩 나오는 상황에서는 brew로 설치할 수 있어 관리가 용이한 AdoptOpenJdk를 설치하려 한다.

설치

1
2
3
4
5
6
7
# 무조건 최신 버전의 OpenJdk 설치
$ brew install --cask adoptopenjdk
 
# 원하는 버전의 OpenJdk를 선택하여 설치
# 설치 가능한 버전 확인은 brew search adoptopenjdk
$ brew tap AdoptOpenJDK/openjdk
# brew install --cask <version>
$ brew install --cask adoptopenjdk11
cs

설치가 완료된 후 아래의 이미지와 같이 java -version 명령어를 이용해 정상적으로 설치되었는지 확인이 가능하다.

참고

 

AdoptOpenJDK/homebrew-openjdk

AdoptOpenJDK HomeBrew Tap. Contribute to AdoptOpenJDK/homebrew-openjdk development by creating an account on GitHub.

github.com

 

반응형

'Dev > Java' 카테고리의 다른 글

[Intellij] Maven - Error:java: error: release version 5 not supported 에러  (0) 2021.07.02
[Java] 용어정리.zip  (0) 2020.09.29
[Java] Stateless Object  (0) 2020.04.11

개요

사소하게 쓰이는 단어들이지만 정확한 정의를 입으로 뱉지 못하는 일이 없도록 정리하고 조금씩 차이가 있는 단어들의 비교해서 명확한 차이를 파악하자!

목차


 

Parameter (매개변수) vs Argument (인수)

Effective Java를 읽다보면 매개변수와 인수라는 단어가 자주 나오는데 생각해보니 둘을 혼용해서 써왔던 것 같다.
두 단어의 정확한 정의는 아래와 같다.
Parameter : 메서드 선언에 정의한 변수
Argument : 메서드 호출 시 넘기는 '실젯값'

// value - parameter
void add(int value) { ... }
// 10 - argument
add(10)
반응형

도메인

소프트웨어로 해결하고자 하는 문제영역을 도메인이라고 한다.
도메인은 하위 도메인으로 이루어 지기도 하는데, 예를 들어 주문이라는 도메인에는 혜택, 결제, 배송, 정산이라는 하위 도메인이 존재한다. 그러나 모든 도메인이 하위 도메인이 존재하는 것은 아니므로, 상황에 따라 하위 도메인 구성을 판단하면 된다.


도메인 모델

특정 도메인을 개념적으로 표현한 것을 도메인 모델이라고 한다.
도메인을 이해하기 위해서는 제공하는 기능과 도메인의 주요 데이터 구성을 파악해야 하는데, 이런 면에서 기능과 데이터를 함께 보여주는 객체 모델은 도메인을 모델링하기에 적합하다.
도메인 모델을 모델링 하는 방법은 객체 모델 외에도 상태 다이어그램이나 UML 표기기법 외에도 그래프(관계가 중요한 도메인일 시), 수학 공식(계산 규칙이 중요한 도메인일 시)을 이용해서 도메인을 모델링할 수 있다.
도메인 모델은 표현 방식이 중요한 것이 아니라 도메인을 잘 이해할 수 있도록 하는 개념 모델이다.
개념 모델은 바로 코드를 작성하기에 적합하지 않으므로 개념 모델을 최대한 따르는 구현 모델이 추가적으로 필요하다.


도메인 모델 패턴

일반적인 어플리케이션 아키텍처는 네 개의 계층으로 나뉜다.

  • 표현 - 사용자의 요청을 처리, 응답
  • 응용 - 직덥 도메인 로직을 가지지 않고 도메인 계층을 조합해서 사용자 요청에 대한 기능을 실행
  • 도메인 - 시스템이 제공할 도메인의 규칙을 구현
  • 인프라스트럭처 - DB와 같은 외부 시스템과 연동

도메인 모델 도출

개발자들은 기획서, 유즈케이스, 사용자 스토리 등을 통해 요구사항의 도메인을 이해한 후에 이를 통해 도메인 모델을 만들고 개발을 시작해야 한다.
도메인 모델링의 기본 작업?

  • 모델을 구성하는 핵심요소, 규칙, 기능을 찾기

엔티티와 밸류

도메인 모델은 두가지로 구분할 수 있음

Entity

  • 식별자를 가진다는 것이 큰 특징
  • 식별자는 바뀌지 않고 고유함
    • 엔티티를 구현한 클래스는 식별자를 이용해 equals(), hashCode()메서드를 구현할 수 있음

Entity 식별자 생성

Entity 식별자를 생성하는 시점은 도메인의 특징과 사용하는 기술에 따라 달라진다.

  • 특정 규칙에 따라 생성
  • UUID(Universally Unique Identifier) 사용
    • 다수의 개발 언어는 UUID 생성기를 제공
  • 값을 직접 입력
    • e.g.) 회원 아이디 / 이메일 : 중복 케이스 발생 가능성이 있으므로 중복 입력을 막는 로직이 필요
  • 일련번호 사용(시퀀스 / DB auto increment)

Value type

Value type은 개념적으로 완전한 하나(!??!?!!!)를 표현할 떄 사용

Bad :

public class ShippingInfo {
  // 받는 사람
  private String receiverName;
  private String receiverPhoneNumber;

  // 주소
  private String shippingAddress1; 
  private String shippingAddress2;
  private String shippingZipcode;

  ...
}

Good : 받는 사람, 주소 자체가 하나의 도메인 개념이므로 Value type을 사용함으로써 개념적으로 온전한 하나의 도메인을 표현

public class Receiver {
  private String name;
  private String phoneNumber;

  ...
}

public class Address {
  private String address1; 
  private String address2;
  private String zipcode;

  ...
}

public class ShippingInfo {
  private Receiver receiver;
  private Address address;

  ...
}

Value type의 장점

  • 해당 데이터(값/변수)의 도메인(개념/의미)을 명확하게 명시할 수 있음
  • 해당 Value type을 위한 기능 추가 가능

Value type 사용시 특징

  • 데이터 변경 시 해당 인스턴스의 값을 변경하는 것이 아닌 변경 할 값을 가지는 새로운 인스턴스를 생성하는 방식을 선호 -> 불변 객체로 사용하라!
    • 의도치 않은 곳에서 데이터가 변경되는 것을 방지하기 위함
  • Value 객체 비교 시, 모든 속성이 같은지 비교해야 함

엔티티 식별자와 밸류 타입

일반적으로 식별자는 String 타입이지만, 식별자가 도메인 특성을 가지는 경우 식별자를 위한 Value type을 사용해서 의미가 잘 드러나도록 한다.

도메인 모델에 set 메서드 넣지 않기

  • get/set 메서드로 인해(특히 set!) 값이 어디서든 바뀔 수 있기 때문에 도메인 핵심 개념이나 의도가 코드에서 사라지게 됨
  • 기본 생성자에 set 메서드로 값을 전달하다보면 객체 내부에 누락되는 데이터가 존재할 수 있음
    • 도메인 객체는 생성 시점에 필요한 것을 전달!

도메인 용어

도메인에서 사용하는 용어는 코드에 그대로 사용해야 가독성도 좋아지고 버그도 줄어들게 된다.

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 10장. 클래스  (0) 2020.05.31
[CleanCode] 9장. 단위 테스트  (0) 2020.05.18
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15

클래스 체계

클래스 정의 표준 자바 관례에 따른 클래스 구성 순서

  • public satic 상수
  • private static 변수
  • private instance 변수
  • public 함수
  • private 함수 - 자신을 호출하는 public 함수 직후에 위치

캡슐화

변수와 유틸리티 함수는 가능한 공개하지 않아야 함
테스트를 위해 protected 선언/패키지 전체 공개, but!!!! 캡슐화를 풀어주는 결정은 최후의 수단


클래스는 작아야 한다!

클래스를 만들때의 규칙은 무조건 작게!
얼마나 작아야 할까?
함수와는 다르게 맡은 책임의 수가 기준

클래스의 책임?
네이밍이 나타낸다. 구현 과정에서 간결한 이름이 떠오르지 않거나 클래스 이름이 모호하다는 것은 책임이 많다는 반증

  • 단일 책임의 원칙

    Single Responsibility Principle
    클래스나 모듈을 변경할 이유가 단 하나뿐이어야 한다

  • Bad : 버전에 대한 책임과 Component에 대한 책임을 모두 가지고 있음*

     public class SuperDashboard extends JFrame implements MetaDateUser {
       public Component getLastFocusedComponent()
       public void setLastFocused(Component lastFocused)
       public int getMajorVersionNumber()
       public int getMinorVersionNumber()
       public int getBuildNumber()
     }

    Good : SuperDashboard 클래스는 Component에 대한 책임만을 갖고 별도의 Version에 대한 책임을 가지는 클래스를 분리

       public class Version {
         public int getMajorVersionNumber()
         public int getMinorVersionNumber()
         public int getBuildNumber()
       }
    
       public class SuperDashboard extends JFrame implements MetaDateUser {
         public Component getLastFocusedComponent()
         public void setLastFocused(Component lastFocused)
       }
  • 응집도

    클래스는 인스턴스 변수 수가 적어야 함
    메서드가 클래스 변수를 많이 사용 할수록 클래스는 응집도가 더 높아짐
    응집도가 높다? 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미이기도 함
    몇몇 메서드만이 사용하는 인스턴스 변수가 많아지게 되면 클래스를 쪼개야 한다는 신호

  • 응집도를 유지하면 작은 클래스 여럿이 나온다

    큰 함수의 일부를 작은 함수로 나눌 수 있을 때 나눌 부분에서 사용되는 변수가 큰 함수 내의 다른 곳에서 사용되지 않으면 클래스로 분리가 가능하다.
    클래스로 분리하였을 때 응집도가 떨어진다면 다시 클래스를 쪼개야 한다.


변경하기 쉬운 클래스

깨끗한 시스템? 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춤
새 기능을 추가할 때 시스템을 확장할 뿐 기존 코드를 변경하지 않도록 클래스를 설계하는 것이 매우 중요!!!

Bad :아래의 클래스는 새로운 SQL문을 지원할 때(책임1), 기존 SQL문을 수정할 때(책임2) 수정이 필요하므로 SRP 위반

public class Sql {
    public Sql(String table, Column[] columns)
    public String create()
    public String insert(Object[] fields)
    public String selectAll()
    public String findByKey(String keyColumn, String keyValue)
    public String select(Column column, String pattern)
    public String select(Criteria criteria)
    public String preparedInsert()
  // 일부 메서드에서만 사용되는 비공개 메서드는 코드 개선의 잠재적인 여지를 시사
    private String columnList(Column[] columns)
    private String valuesList(Object[] fields, final Column[] columns) private String selectWithCriteria(String criteria)
    private String placeholderList(Column[] columns)
}

Good :
공개 인터페이스를 전부 SQL 클래스에서 파생하는 클래스로 분리
비공개 메서드는 해당 메서드를 사용하는 클래스로 이동
공통된 인터페이스는 따로 클래스로 분리
아래와 같이 분리 후에는 새로운 SQL문 추가 시, 기존 SQL문 수정 시에 기존의 클래스를 건드릴 이유가 없어짐

abstract public class Sql {
    public Sql(String table, Column[] columns) 
    abstract public String generate();
}

public class CreateSql extends Sql {
    public CreateSql(String table, Column[] columns) 
    @Override public String generate()
}

public class SelectSql extends Sql {
    public SelectSql(String table, Column[] columns) 
    @Override public String generate()
}

public class InsertSql extends Sql {
    public InsertSql(String table, Column[] columns, Object[] fields) 
    @Override public String generate()
    private String valuesList(Object[] fields, final Column[] columns)
}

public class SelectWithCriteriaSql extends Sql { 
    public SelectWithCriteriaSql(
    String table, Column[] columns, Criteria criteria) 
    @Override public String generate()
}

public class SelectWithMatchSql extends Sql { 
    public SelectWithMatchSql(String table, Column[] columns, Column column, String pattern) 
    @Override public String generate()
}

public class FindByKeySql extends Sql public FindByKeySql(
    String table, Column[] columns, String keyColumn, String keyValue) 
    @Override public String generate()
}

public class PreparedInsertSql extends Sql {
    public PreparedInsertSql(String table, Column[] columns) 
    @Override public String generate() {
    private String placeholderList(Column[] columns)
}

public class Where {
    public Where(String criteria) public String generate()
}

public class ColumnList {
    public ColumnList(Column[] columns) public String generate()
}

결과적으로 OCP(Open-Closed-Priciple)도 지킬 수 있게됨
OCP란? 확장에 개방적이로 수정에 폐쇄적이어야 한다는 객체 지향 설계의 핵심 원칙 중 하나

  • 변경으로부터 격리
    인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리
    매번 달라지는 API로 테스트 코드를 짜기 힘듦 -> API를 구현하는 기능을 인터페이스로 분리하라!
    테스트 시 해당 인터페이스를 상속받아 API를 흉내내는 테스트용 클래스를 만들어 테스트가 가능해짐
    e.g.,

    public interface stockExchange {
      Money currentPrice(String symbol);  // API의 결과값을 뱉어주는 메서드
    }
    
    public Portfolio {
      private StockExchange exchange;
      public Portfolio(StockExchange exchange) {
        this.exchange = exchange;
      }
      // ...
    }
    
    public class PortfolioTest {
      private FixedStockExchangeStub exchange;
      private Portfolio portfolio;
    
      @Before
      protected void setUp() throws Exception {
        exchange = new FixedStockExchangeStub();
        exchange.fix("MSFT", 100);
        portfolio = new Portfolio(exchange);
      }
    
      @Test
      public void GivenFiveMSFTTotalShouldBe500() throws Exception {
        portfolio.add(5, "MSFT");
        Assert.assertEquals(500, portfolio.value());
      }
    }

    결합도가 낮아짐!
    -> 각 시스템 요소(클래스)가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미
    결합도가 낮아지면 자연스럽게 DIP(Dependency-Inversion-Priciple)을 따르는 클래스가 구현 됨
    DIP란? 클래스가 상세한 구현이 아니라 추상화에 의존해 다른 클래스의 변경에 영향받지 않도록 한다는 원칙

반응형

'Dev > Books' 카테고리의 다른 글

[DDD Start!] 1장. 도메인 모델 시작  (0) 2020.06.06
[CleanCode] 9장. 단위 테스트  (0) 2020.05.18
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15

목차

  1. TDD 법칙 세 가지
  2. 깨끗한 테스트 코드 유지하기
  3. 깨끗한 테스트 코드
  4. 테스트 당 assert 하나
  5. F.I.R.S.T

TDD 법칙 세 가지

  1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

깨끗한 테스트 코드 유지하기

깨끗하지 않은 테스트 코드는 유지 보수에 많은 비용이 필요하게 되고, 테스트 코드가 없으면 프로덕션 코드의 결함율이 높아지게 된다.
테스트 코드는 유연성, 유지보수성, 재사용성을 제공해야 한다.


깨끗한 테스트 코드

깨끗한 테스트 코드는 명료성, 단순성, 풍부한 표현을 바탕으로 한 가독성이 필수이다.


테스트 당 assert 하나

assert 하나만을 사용하기 위해 아래와 같은 방법을 사용 할 수 있다.

  • 테스트를 쪼개서 assert를 분리
  • Template Method 패턴을 사용해 중복을 제거
  • @Before 함수에 given/when 부분을 분리

테스트 함수 하나는 개념 하나만 테스트 해야 한다!


F.I.R.S.T

  • Fast
  • Independent
  • Repeatable
    네트워크가 없는 환경에서도 실행 가능해야 함
  • Self-Validating
    테스트는 성공 혹은 실패로만 결과가 나와야 한다. 별도의 log 등을 통한 확인 작업은 절대 금물
  • Timely
    테스트 코드 작성 -> 실제 코드 작성


참고

BUILD-OPERATE-CHECK 패턴

Given-When-Then 패턴과도 비슷

  • Build (Given)
    Input 데이터를 생성
  • Operate (When)
    Build 단계에서 생성한 데이터로 실제 코드 실행
  • Check (Then)
    Operate 단계의 결과값을 확인

e.g.

public class BowlingTest {
  @Test
  public void FrameTest() {
    // Build (Given)
    Boll testBoll = Boll.getInstance(10);
    Frame frame = Frame.getInstance(Boll);
    // Operate (When)
       Boolean isStrike = frame.isStrike();
    // Check (Then)
    assertThat(isStrike).isTrue();
  }
}

TEMPLATE METHOD 패턴

어떠한 기능의 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계의 수행 내역을 바꾸는 패턴
e.g.

Original :

public class SumoDeadLift {
  final void start() {
    stand();
    getBarbell();
    doOneRep();
  }

  private void stand() {  // 각 DeadLift 별로 수행 내역이 다른 부분
      System.out.println("다리를 넓게 벌리고 선다.");
  }

  private void getBarbell() {  // 모든 DeadLift의 공통된 알고리즘
    System.out.println("바벨을 들어올린다.");
  }

  private void doOneRep() {
    System.out.println("바벨을 다시 바닥으로 내려놓는다.");
  }
}

public class RumanianDeadLift {
  final void start() {
    stand();
    getBarbell();
    doOneRep();
  }

  private void stand() {
      System.out.println("다리를 골반 넓이 만큼 벌리고 선다.");
  }

  private void getBarbell() {
    System.out.println("바벨을 들어올린다.");
  }

  private void doOneRep() {
    System.out.println("정강이 중간까지만 내려간다");
  }
}

With Template Method Pattern: 모든 DeadLift의 수행 내역이 같은 부분은 추상 클래스에 정의한다.

public abstract class DeadLift {
  final void start() {
    stand();
    getBarbell();
    doOneRep();
  }

  abstract void stand();

  private void getBarbell() {  // 모든 DeadLift의 공통된 알고리즘
    System.out.println("바벨을 들어올린다.");
  }

  abstract void doOneRep();
}

public class SumoDeadLift extends DeadLift {
  @Override
  private void stand() {  // 각 DeadLift 별로 수행 내역이 다른 부분
      System.out.println("다리를 넓게 벌리고 선다.");
  }

  @Override
  private void doOneRep() {
    System.out.println("바벨을 다시 바닥으로 내려놓는다.");
  }
}

public class RumanianDeadLift extends DeadLift {
  @Override  
  private void stand() {
      System.out.println("다리를 골반 넓이 만큼 벌리고 선다.");
  }

  @Override
  private void doOneRep() {
    System.out.println("정강이 중간까지만 내려간다");
  }
}
반응형

'Dev > Books' 카테고리의 다른 글

[DDD Start!] 1장. 도메인 모델 시작  (0) 2020.06.06
[CleanCode] 10장. 클래스  (0) 2020.05.31
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15

목차

  1. 오류 코드보다 예외를 사용하라
  2. Try-Catch-Finally 문부터 작성하라
  3. 미확인 예외를 사용하라
  4. 예외에 의미를 제공하라
  5. 호출자를 고려해 예외 클래스를 정의하라
  6. 정상 흐름을 정의하라
  7. null을 반환하지 마라
  8. null을 전달하지 마라

오류 코드보다 예외를 사용하라

오류 코드를 받아 처리 로직을 추가하는 것보다 오류가 발생하면 예외를 던지는게 좋음
Bad : 함수를 호출한 즉시 오류를 확인하지 않으면 문제가 발생할 확률이 높음

public class DeviceController {
  ...
  public void sendShutDown() {
    DeviceHandle handle = getHandle(DEV1);
    if (handle != DeviceHandle.INVALID) {
      retrieveDeviceRecord(handle);
      if (record.getStatus() != DEVICE_SUSPEND) {
        pauseDevice(handle);
        ...
      } else {
        logger.log("Device suspend.");
      }
    } else {
      logger.log("Invalid handle for: ");
    }
  }
}

Good :

public class DeviceController {
  ...
  public void sendShutDown() {
    try {
      tryToShutDown();
    } carch (DeviceShutDownError e) {
      logger.log(e);
    }
  }

  private void tryToShutDown() throws DeviceShutDownError {
    DeviceHandle handle = getHandle(DEV1);
    DeviceHandle record = retrieveDeviceRecord(handle);

    pauseDevice(handle);
    ...
  }

  private DeviceHandle getHandle(DeviceID id) {
    ...
       throw new DeviceShutDownError("Invalid handle for: ");
    ...
  }
}

Try-Catch-Finally 문부터 작성하라

try-catch-finally 문으로 시작하면 try 블록에서 무슨 일이 생기는지 호출자가 기대하는 상태를 정의하기 쉬워진다.


미확인 예외를 사용하라

확인된 예외

OCP(Open Closed Principle)를 위반하고 캡슐화가 꺠짐
예외를 던지는 메서드가 catch 블록이 있는 메서드가 아닌 더 하위에 있다면 그 사이의 메서드에서 모두 해당 예외를 선언부에 추가하거나 catch 블록에서 처리해야 함

public void get() {
  try {

  } catch (InvalidGetException e) {
    logger.log(e);
  }
}

public void getById() throws InvalidGetException {
  call();
}

public void call() throws InvalidGetException {
  throw new InvalidGetException();
}

예외에 의미를 제공하라

호출 스택만으로 사용자가 의도를 파악하기 어려우므로 오류 메세지에 정보를 담아 예외와 함께 던져야 한다.


호출자를 고려해 예외 클래스를 정의하라

프로그래머는 오류를 정의할 때 오류를 잡아내는 방법을 고려해야 한다.
외부 API의 다양한 예외를 직접 노출하지 않고 감싸기 기법을 통해 새로운 클래스를 만들어 캡슐화
Bad : 다른 종류의 예외를 처리 로직은 모두 같으므로 의미가 없음

ACMEPort port = new ACMEPort(12);

try {
  port.open();
} catch (DeviceResponseException e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
  reportPortError(e);
  logger.log("Unlock exception", e);
} catch (GMXError e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} finally {
  ...
}

Good :

public class LocalPort {
  private ACMEPort innerPort;

  public LocalPort(int portNumber) {
    innerPort = new ACMEPort(portNumber);
  }

  public void open() {
    try {
      innerPort.open();
    } catch (DeviceResponseException e) {
      throw new PortDeviceFailure(e);
    } catch (ATM1212UnlockedException e) {
      throw new PortDeviceFailure(e);
    } catch (GMXError e) {
      throw new PortDeviceFailure(e);
    }
  }
  ...
}

정상 흐름을 정의하라

외부 API를 감싸 독자적인 예외를 던져 중단한 뒤 호출하는 코드에서 처리를 정의해 중단된 계산을 처리하는 것은 대게 적합하지만 중단이 적합하지 않은 경우도 있다. 이러한 경우 특수 사례 패턴(SPECIAL CASE PATTERN)을 적용해 개선한다.
특수 사례 패턴이란?
반환할 값이 없을 때 예외를 던지는 것이 아니라 기본값을 반환

Bad :

try {
  MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
  m_total += expenses.getTotal();
} catch (MealExpensesNotFound e) {
  m_total += getMealPerDien();
}

Good :

public int testMethod() {
  ...
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getId());
    m_total += expenses.getTotal();
  ...
}

public class PerDiemMealExpenses implements MealExpenses {
  private static final int MEAL_EXPENSES_DEFAULT = 3000;
  public int getTotal() {
        ...
    // 반환 값이 없을 경우 기본값으로 일일 기본 식비를 반환한다.
    return MEAL_EXPENSES_DEFAULT;
  }
}

null을 반환하지 마라

메서드가 null을 반환하면 해당 메서드를 사용하는 클라이언트는 null 체크 코드를 추가할 수 밖에 없다. 이 과정에서 null 체크가 누락되면 버그로 이어지고, null 체크 코드는 아름답지 못하다. 특수 사례 객체를 통해 이러한 문제를 해결할 수 있다.
Bad :

List<Employee> employees = getEmployees();
if (eployees != null) {
  for (Employee e : employees) {
    totalPay += e.getPay();
  }
}

Good :

List<Employee> employees = getEmployees();
for (Employee e : employees) {
  totalPay += e.getPay();
}

public List<Employee> getEmployees() {
  if (.. 직원이 없다면 ..) {
    return Collections.emptyList();
  }
}

null을 전환하지 마라

메서드의 argument로 null을 전달하게 되면 메서드는 내부에서 null 체크 또는 assert를 이용하여 처리하는 로직을 추가할 수 밖에 없다. 이러한 로직을 통해 exception을 던지는 메서드가 되면 해당 메서드를 사용하는 쪽에서도 exception 처리를 추가해야 한다.
호출자가 넘기는 null을 처리하는 방법은 없다. 정책적으로 null을 넘기지 못하게 하는것이 가장 합리적이다.

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 9장. 단위 테스트  (0) 2020.05.18
[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12

목차

  1. 자료 추상화
  2. 자료/객체 비대칭
  3. 디미터 법칙
  4. 자료 전달 객체

자료 추상화

변수를 함수를 통해 계층을 추가한다고 해서 구현이 저절로 감춰지지는 않는다.
추상 인터페이스를 제공해 사용자가 구현을 모르는 채 자료의 핵심을 조작할 수 있어야 클래스라고 할 수 있다.
Bad :

// 해당 클래스에 getX, getY 메서드를 추가한다 해도 사용자는 x, y를 반환할 뿐일 것이라고 추측이 가능하다  
// 이것은 전혀 추상화되지 않은 상태이다
public class Point {
  public double x;
  public double y;
}

Good :

// x, y가 실제 직교 좌표계인지, 극좌표계인지 알 수 없다  
// 내부에서 변수가 바뀌던, 로직이 바뀌던 클라이언트는 상관이 없다
public interface Point {
  double getX();
  double getY();
  void setCartesian(double x, double y);
  double getR();
  double getTheta();
  void setPolar(double r, double theta);
}

자료/객체 비대칭

객체 vs 자료 구조

  • 객체
    추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개
  • 자료 구조
    자료를 그대로 공개
    별도의 함수는 제공하지 않음

객체 지향 코드 vs 절차 지향 코드

  • 객체 지향 코드
    기존 함수를 변경하지 않으면서 새 클래스를 추가하기 용이
    새로운 함수를 추가하기가 어려움
  • 절차 지향 코드
    기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 용이
    새로운 자료구조를 추가하기 어려움

필요에 따라 자료구조 + 절차 지향 코드가 객체 지향 코드보다 나을 수 있다. 필요에 따라 사용하자.


디미터 법칙

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙
한 객체의 메서드가 반환하는 객체의 메서드를 호출하면 안됨

  • 기차 충돌
    Bad : 기차 충돌 상황

    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
    
    String outFile = outputdir + "/" + className.replace('.', '/') + ".class";
    FileOutputStream fout = new FileOutputStream();
    BufferedOutputStream bos = new BufferedOutputStream(fout);

    Better :

    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();

    기차 충돌 상황보다는 낫지만 각 메서드가 반환하는 값이 객체인지 자료 구조인지에 따라 디미터 법칙 위반여부가 결정됨
    객체라면 내부 구조를 getter를 통해 노출시키는 것이므로 디미터 법칙을 위반하는 것
    getter가 객체인지 자료 구조인지 알 수 없게 혼동을 주므로 아래와 같이 명확하게 한다.

    Good :

    final String outputDir = ctxt.options.scratchDir.absolutePath;
  • 잡종 구조
    공개 변수가 있으면서 공개 조희/설정 함수가 있고 기능을 수행하는 함수도 있는 구조

  • 구조체 감추기
    기차 충돌 예시에서 ctxt, opts, scratchDir이 진짜 객체라면?
    내부의 객체를 가져다 쓰는 것이 아닌 메세지를 보내 필요한 정보를 반환 받도록 해야 한다.

    // outputDir의 목적이 무엇인지를 확인하여 수정한다.
    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

자료 전달 객체

Data Transfer Object(DTO) - 공개 변수만 있고 함수가 없는 클래스
데이터베이스와 통신하거나 소켓에서 받은 메세지의 구문을 분석할 때 유용

  • bean 구조
    private 변수와 생성자, gettter만 존재
  • 활성 레코드
    DTO의 일반적인 형태에 save, find와 같은 탐색 함수도 제공
    데이터베이스 테이블이나 다른 소스에서 자료를 직접 변환한 결과
    비즈니스 규칙 메서드를 직접 추가하지 않도록 조심해야 함
반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 8장. 경계  (0) 2020.05.18
[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 3장. 함수  (0) 2020.05.06

목차

  1. 형식을 맞추는 목적
  2. 적절한 행 길이를 유지하라
  3. 가로 형식 맞추기
  4. 팀 규칙

프로그래머는 규칙에 맞게 형식을 깔끔하게 맞춰 코드를 짜야 한다.
팀으로 일한다면 팀이 합의해 규칙을 정하고 모두가 그 규칙을 따라야 한다.


형식을 맞추는 목적

맨 처음 잡아놓은 코드의 스타일과 가독성은 유지보수 용이성과 확정성에 계속 영향을 미친다.


적절한 행 길이를 유지하라

  • 신문 기사처럼 작성하라
    소스 파일은 고차원 개념/알고리즘 -> 저차원 함수와 세부 내역 순으로 아래로 내려갈수록 의도를 세세하게 표현
  • 개념은 빈 행으로 분리하라
  • 세로 밀집도
    서로 밀접한 코드 행은 세로로 가까이 놓여야 함
  • 수직 거리
    변수 선언 - 사용하는 위치에 최대한 가까이 선언
    인스턴스 변수 - 클래스의 맨 처음에 선언, 변수 간에 세로로 거리를 두지 않음
    종속 함수 - 한 함수에서 호출하는 다른 함수는 세로로 가까이 배치 (함수 -> 호출되는 함수 순서)
    개념적 유사성 - 친화도가 높을수록 코드를 가까이 배치 (한 함수가 다른 함수를 호출하는 직접적인 종속성, 변수와 그 변수를 사용하는 함수, 명명법이 똑같거나 기능이 같은 함수)
  • 세로 순서
    함수 호출 종속성은 아래 방향으로 유지, 자연스럽게 소스 코드 모듈이 고차원 -> 저차원으로 기술

가로 형식 맞추기

Max 120자

  • 가로 공백과 밀집도
    공백을 통해 개념을 분리 (e.g., 할당 연산자, parameter 분리를 위한 ", ")
    연산자 우선순위 구분을 위한 공백 추가
  • 가로 정렬
    정렬이 필요할 정도로 목록이 길다면 클래스를 쪼개는 방법을 고려
  • 들여쓰기
    scope로 이뤄진 계층을 표현하기 위해 사용
    1줄의 if/while/함수도 꼭 {} 괄호를 사용하며 들여쓰기를 사용
  • 가짜 범위
    빈 while/for문은 다음 행에 세미콜론을 들여써서 작성

팀 규칙

팀에 속해 있다면?
각자의 선호하는 규칙 < 팀 규칙

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 3장. 함수  (0) 2020.05.06
[CleanCode] 2장. 의미있는 이름  (0) 2020.05.02

목차

  1. 작게 만들어라!
  2. 한 가지만 해라!
  3. 함수 당 추상화 수준은 하나로!
  4. Switch 문
  5. 서술적인 이름을 사용하라
  6. 함수 인수
  7. 부수 효과를 일으키지 마라!
  8. 명령과 조회를 분리하라!
  9. 오류 코드보다 예외를 사용하라!
  10. 반복하지 마라!
  11. 구조적 프로그래밍
  12. 함수를 어떻게 짜죠?
  13. 결론

작게 만들어라!

작게의 기준은 무엇일까?

  • 블록과 들여쓰기
    if/else/switch 문 등에 들어가는 블록은 한 줄이며 indent는 2단을 넘어서면 안된다.
    적절한 메서드명을 가지는 메서드를 호출하는 방식을 통해 이를 충족시킨다.

한 가지만 해라!

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.

함수는 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
한 함수에서 섹션을 나눌 수 없다면 함수가 한 가지 작업을 한다고 할 수 있다.

함수 당 추상화 수준은 하나로!

함수 내에 추상화 수준이 섞이면 특정 표현이 근본 개념인지 세부사항인지 구분하기 어려워진다.

  • 위에서 아래로 코드 읽기: 내려가기 규칙
    한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 오면 함수 추상화 수준이 한번에 한 단계씩 낮아진다. 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.

Switch 문

Bad :

public Money calculatePay(Employee e) throws InvalidEmployeeType {
  switch (e.type) {
    case COMMISSIONED:
      return calculateCommissionedPay(e);
    case HOURLY:
      return calculateHourlyPay(e);
    case SALARIED:
      return calculateSalariedPay(e);
    default:
      throw new InvalidEmployeeType(e.type);
  }
}

Switch문의 문제점

  1. 함수가 길어진다
  2. '한 가지' 작업만 수행하지 않는다.
  3. Single Response Principle를 위반한다.
  4. Open Closed Principle을 위반한다.

Switch문은 다형적 객체를 생성하는 코드안에서만 사용하도록 해야 한다.

서술적인 이름을 사용하라

일관적이고 서술적인 이름을 사용하여 함수 이름만으로 해당 함수가 하는 일을 파악할 수 있도록 해야 한다.

함수 인수

테스트 코드 관점에서도 인자가 많을수록 테스트를 위한 유효한 인자를 만들어야 하는 문제가 생긴다.
가장 이상적인 인수 개수는 0개, 최대 3개. 하지만 적을수록 좋다. 줄이자 줄이자!

  • 많이 쓰는 단항 형식
    • 인수에 질문을 던지는 경우
    • 인수를 뭔가로 변환해 결과를 반환하는 경우
    • 이벤트 (출력은 없는 경우, 다소 드물다) - 이름과 문맥을 통해 이벤트 함수임을 명확히 드러내야 한다.
  • 플래그 인수
    플래그 인수 값에 따라 메서드가 다른 기능을 하게 된다는 의미이므로 플래그 인수는 지양해야 한다.
  • 이항 함수
    무시해도 되는 인자가 있는 순간 버그 확률이 늘어난다.
    이항 함수가 적절한 경우는 인수 2개가 한 값을 표현하는 두 요소인 경우, 자연적인 순서가 있는 경우이다.
    같은 클래스인 2개의 인자를 받는 경우 순서를 헷갈릴 수 있으므로 지양해야 한다.
  • 삼항 함수
    순서, 주춤, 무시로 야기되는 문제는 두 배 이상으로 늘어난다.
    인자의 의미가 명확한 경우가 아니면 지양한다.
  • 인수 객체
    결국 변수에 이름을 붙여야 하므로 개념을 표현하게 되는 것이다.
    e.g.,
    Circle makeCircle(double x, double y, double radius);
    Circle makeCircle(Point center, double radius);
  • 인수 목록
    가변 인수 전부가 동등하게 취급될 경우 List 형 인수 하나로 취급할 수 있다.
    e.g., public String format(String format, Object... args)
  • 동사와 키워드
    함수의 의도나 인수의 순서와 의도를 함수 이름에 담아야 한다.
    함수 이름에 인자의 키워드를 추가하여 인자의 의미와 순서를 보장 해 준다.

부수 효과를 일으키지 마라!

함수는 꼭 한 가지 기능만을 한다.
부수 효과로 발생 가능한 문제?

  • Temporal Coupling
  • Order Dependency

명령과 조회를 분리하라!

함수의 역할

  • 뭔가를 수행한다.
  • 뭔가에 답한다.

Bad :

// attribute인 속성을 찾아 값을 value로 설정 후 성공시 true, 실패시 false
public boolean set(String attribute, String value);

if (set("username", "unclebob")) ...

Good :

if (attributeExist("username")) {
  setAttribute("username", "unclebob");
}

오류 코드보다 예외를 사용하라!

오류코드의 문제점

  • 호출자는 오류 코드를 바로 처리해야 한다.
  • 의존성 자석 : 오류 코드의 수정/추가/삭제가 일어날 경우 처리 코드도 수정해야 한다

Try/Catch 블록 뽑아내기
오류처리도 한 가지 작업임을 인지하고 오류 처리 함수를 분리하도록 한다.

Bad : try/catch 블록은 정상 동작과 오류 처리 동작을 섞으며 코드 구조에 혼란을 일으킨다.

try {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
  logger.log(e.getMessage());
}

Good :

public void delete(Page page) {
  try {
    deletePageAndAllReferences(page);
  } catch (Exception e) {
    logError(e);
  }
}

// 정상 동작만을 처리한다.
private void deletePageAndAllReferences(Page page) throws Exception {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
  logger.log(e.getMessage());
}

반복하지 마라!

반복되는 코드가 다른 코드와 섞이면 모양이 달라지면서 중복이 잘 드러나지 않게 되어 수정이 일어날 때 오류가 발생할 확률이 높아진다.

중복을 없애거나 제어 할 목적의 원칙과 기법들

  • 관계형 데이터베이스에 정규 형식을 만듦
  • 객체지향 프로그래밍 - 코드를 부모 클래스로 몰아 중복을 없앰
  • 구조적 프로그래밍
  • AOP(Aspect Oriented Programming) - Spring의 핵심 개념 중 하나로 부가기능의 모듈화 (하단 링크들 참고)
  • COP(Component Oriented Programming) - 기존 시스템이나 소프트웨어를 구성하는 컴포넌트를 조립해서 하나의 새로운 응용프로그램을 만드는 소프트웨어 개발 방법론

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 entry와 exit이 하나만 존재해야 한다. 즉, return 문이 하나여야 한다. 루프 안에서 break, continue를 사용해서는 안 되며 goto는 절대로 안된다.

함수가 클 때만 이익을 제공하는 기법이다.
함수가 작을때는 return, break, continue를 여러차례 사용해도 괜찮지만 goto는 작은 함수에서도 피해야 한다.

함수를 어떻게 짜죠?

TDD
테스트 코드 -> 중복, 네이밍, 들여쓰기 등 상관없이 우선 기능을 개발 -> 리팩토링 (무조건 단위테스트는 계속 통과)
위 과정을 반복한다.

결론

위의 규칙들을 잘 따라 시스템을 잘 풀어가야한다!


참고하면 좋을 문서

AOP

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 2장. 의미있는 이름  (0) 2020.05.02

+ Recent posts