클린코드 9장 (클래스), 10장 (시스템), 11장(창발성), 12장(동시성)

JAVA/클린코드|2020. 7. 10. 09:14

클래스


  • 내부에서 동작하는 변수나 유틸리티는 protected로 선언하여 테스트에 사용하기도 한다.
  • 클래스에 기본 규칙은 작게 만드는 것. 많을수록 클래스에 대한 책임이 너무 커진다.
    SPR을 지키기 위해 클래스가 너무 많아지면 사용에 더 어렵다고 우려할 수 있으나 하나의 여러 책임을 가지고 있는 클래스를 사용하는 것 보다 더 직관적 이므로 사용하기 더 편하다.
  • 클래스 내부에 인스턴스 변수가 많아 진다는건 결국 클래스 내부에 함수와 인스턴스 변수들 사이에 응집도가 높아진다는 뜻이다. 이럴 수록 클래스를 분리해야 한다는 걸 의미한다.
  • 긴 함수를 쪼갠다 → 작은 함수 여러 개로 만든다. → 몇몇 변수와 몇몇 함수만 사용 되는 경우가 보이면 클래스로 쪼갠다.
  • 특정 기능이 변경 될 때 마다 코드가 변경되어야 하는 함수가 있다고 한다면 이는 SRP를 위반하는 행위이다. 그럴 경우 인터페이스를 만들어 기능 마다 필요한 코드를 구현하도록 하면 SRP와 OCP를 모두 위반하지 않을 수 있다.
  • 인터페이스를 선언하고 구현 클래스를 사용하는 것이 깔끔한 경우도 있으나 특정 기능이 매번 바뀌어야 하는 경우에는 변경의 위험이 있을 수 있다. 예를 들어 상황에 따라서 호출하는 api가 변경될 수 있는 경우에는 구현 클래스 레벨에서 모두 할 것이 아니라 api를 호출하는 인터페이스를 전달 받아서 처리하는 것이 더 좋다.
public interface StockExchange {
	Money currentPrice(String symbol);
}

public Portfolio {
	private StockExchange stockExchange;
	public Portfolio (StockExchange stockExchange) {
		this.stockExchange = stockExchange;
	}

	//...
}

 

 

 

 

시스템


  • 모든 애플리케이션에서 가장 중요한 부분은 관심사 분리이다.
    아래와 같은 코드의 경우 Lazy Initialization 또는 Lazy Evaluation을 사용하여 실제 필요한 순간에 할당하여 사용할 수 있다는 장점이 있으나 테스트 할 때 런타임 시에 객체 생성 로직이 합쳐져 있어서 Mock 작업도 진행해 줘야 하고 정상적으로 생성 되는지도 테스트 해야하는 두 가지의 책임이 생기기에 이는 SRP를 위반하는 행위이다. 이처럼 시스템 설정 부분과 런타임 시 필요한 부분이 연동되어 있으면 안된다. 모듈화와 관심사 분리가 잘 고려되어야 한다.
public Service getService() {
	if (this.service == null) 
		this.service = new WedulServiceImpl();
	return this.service;
}
  • Main과 application은 서로 관심사가 분리되어 있어 main은 적절하게 객체를 생성하고 application은 그 것이 잘 생성되었다는 걸 가정하에 실행하도록 하여 시스템 설정과 실행을 분리하도록 한다.
  • 객체는 인스턴스로 만드는 책임을 지지 않고 이에 대한 책임은 main이나 특수 컨테이너 제공하는 것을 DI (의존성 주입)이라고 한다.
  • 시스템은 깨끗해야하고 깨끗하지 못한 아키텍처는 도메인 논리를 흐리며 기민성을 떨어뜨린다. 도메인 논리가 흐려지면 제품 품질이 떨이지고 버그가 숨어들기 쉬워지고 생산성이 떨어진다.
  • 시스템이든 모듈이든 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자.

 

 

 

창발성


설계 규칙 4가지

 

 

모든 테스트를 실행하라.

  • 테스트를 통해 의도대로 돌아가는지 확인하는 건 당연한 역할
  • 테스트를 진행하다 보면 설계 품질은 더불어서 상승한다.
  • 결합도가 높으면 테스트가 어렵기 때문에 테스트를 작성하면서 DIP, DI, 인터페이스, 추상화 등과 같은 도구를 사용해 결합도를 낮출 수 있다.
  • 완벽한 테스트 코드를 만들고 나면 코드를 수정하는 리팩토링 과정을 거친다 하더라도 검증이 잘 되기 때문에 기존 코드가 잘 동작하지 않을거라는 불안에서 벗어 날 수 있다. 그래서 장기적으로 보나 단기적으로 보나 테스트 코드는 중요하다.
  • 핵심은 응집도를 높이고, 결합도를 낮추고, 관심사를 분리하고, 시스템 관심사를 모듈로 나누고, 함수와 클래스 크기를 줄이고, 더 나은 이름을 선택하는 것.

 

중복을 없애라

  • 같은 기능을 여러곳에서 생성하는 건 수정이 있을 때 여러곳에 작업이 필요로 하다.
  • 중복을 해결 하기 위해서는 TEMPLATE METHOD 패턴을 참고하라.
// 중복으로 들어가는 코드는 추상 클래스 내부에서 정의하고 
// 개별적으로 사용되는 부분만 abstract 메소드 선언한 뒤, 그 코드들을 공용으로 쓰는 메소드를 만들어라.



// 직원들 임금을 정하는 템플릿 클래스
public abstract class Pay {

  public int salary() {
	return commonSalary() + ratingBouns();
  }

  public int commonSalary() {
	return 100000;
  }

  abstract protected int ratingBonus();
}

// 정규직 임금
public class regularPay extends Pay {

  @Override
  protected int ratingBonus() {
	return 12321;
  }
}

// 비정규직 임금
public class temporaryPay extends Pay {
  
  @Override
  protected int ratingBonus() {
	return 21251;
  }
}

 

의도를 표현하라.

  • 표준 명칭과 가식성 좋은 이름을 사용하고 함수와 클래스 크기등을 잘 조절하여 같은 동료가 이해하기 쉽게 작성하라.

 

클래스와 메서드의 수를 최소로 줄여라

  • 클래스와 메서드 수를 줄이기 위해 무조건 인터페이스먼저 선언하고 진행을 하거나 하는 습관도 좋다.
  • 중복을 제거하고 테스트케이스를 잘 만들어 나가서 클래스와 메서드의 수를 최대한으로 줄여보자.

 

 

 

동시성


  • 대량의 데이터를 동시에 실행시키면 단일로 실행시킬 때 보다 더욱 효율적이다.
  • 원칙적으로 각 서블릿 스레드는 다른 서블릿 스레드와 무관하게 자신만의 세상에서 돌아간다.
  • 동시성은 부하를 유발할 수 있고 복잡하며 버그가 발생할 시 재현이 어렵다.
  • 동시성을 방어하기 위해서는 우선 동작 단위를 작게 하여 SRP를 꼭 지켜야 한다.
  • 그리고 임계영역을 제한하고 그 임계영역을 최대한 작게 만들어서 다른 동작에 영향 없게 설계 해야 한다.
  • ConcurrentHashMap과 같은 다중 스레드 환경에서 안전한 라이브러리를 활용하라.
  • 동기화 코드를 실행할 시 알수 없는 에러가 발생할수도 있는데 이를 단순 일회성 오류라고 생각하지 말고 실패할 수 있는 모든 경우의 테스트를 실행하라.

 

 

 

그 이후 내용은 특정 라이브러리를 개선하는 작업등이 남아있는데 이 부분은 따로 정리하지는 않는다.

결론은 기본에 충실하고 코드의 나쁜 품질을 지키기 위해서 컨벤션 정의가 필요하다고 생각된다. 결국 일은 혼자하는 것이 아니기 때문에 컨벤션 정의가 없으면 새로들어오는 사람이나 기존의 사람도 계속 코드를 유지하기는 어렵다고 판단된다. 이 기회에 이 규칙과 기존에 공부했던 내용들을 토대로 컨벤션을 한번 만들어 봐야겠다.

댓글()

규칙 56 - 일반적으로 통용되는 작명 관습을 따르라

JAVA/Effective Java|2018. 5. 29. 23:50

자바의 작명관습은 두 가지 범주로 나눌 수 있다.

철자.
-> 패키지, 클래스, 인터페이스, 메서드, 필드 그리고 자료형 변수에 관한 것
-> 아주 그럴듯한 이유가 없이 이 규칙을 어겨서는 안 된다.

1). 패키지
-> 마침표를 구분점으로 사용하는 계층적 이름 이어야 한다.
-> 각각의 컴포넌트는 알파벳 소문자로 구성하고, 숫자는 거의 사용하면 안된다.
-> 패키지 시작은 회사 조직의 도메인으로 시작한다. com.wedul
-> 패키지명 컴포넌트는 짧아야 하며, 8자리 이하여야 한다.
-> 약어를 사용하여 의미를 충분히 전달할 수 있어야한다.

2). 클래스, 인터페이스, Enum
-> 하나이상의 단어로 구성된다.
-> 각 첫 글자는 대문자로 시작해야 하며 널리 사용 되는 약어를 제외하고는 약어를 사용해서는 안된다.
-> 단어의 첫 글자만 대문자로 사용하는 것이 좋다.

3). 메서드와 필드
-> 메서드와 필드도 클래스와 인터페이스와 동일한 규칙을 따르지만, 소문자로 시작한다.
-> 상수 필드는 하나 이상의 대문자로 구성되며 단어 마다 _로 구분한다. VALUE_FIELD 
-> 지역 필드에 경우 약어로 사용해도 무관하다. (ex. i, xref..)


문법.
-> 철자 규칙보다는 덜 속박받는 규칙으로 사용된다. (의견이 분분하기 때문에 알맞게 사용하면 된다.)

1) 클래스, enum
-> 명사나, 명사구를 사용한다. 
-> 클래스에 경우 형용사구를 사용하는 경우가 있다. Runnable, Iterable 등등..

2) 메서드
-> 일반적으로 동사, 동사구를 사용하ㅓㄴ다. append 

3) boolean
-> is, has로 시작하여 사용한다.



요약하면, 표준적 작명 관습을 숙지하고 사용하라. 
잘 작성된 명명 규칙만으로도 개발자들 사이에서는 백마디 말보다 더 빠른 이해력을 도울 수 있다. 

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙56 인용.

댓글()

클래스와 인터페이스 - 규칙 18 추상 클래스 대신 인터페이스를 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:36

자바는 다중 상속이 되지 않기 때문에, 추상 클래스 보다 인터페이스를 사용하는 것이 좋다.

믹스인
인터페이스는 믹스인을 정의하는 데 이상적이다.
=> 믹스인은 클래스가 주 자료형 이외에 추가로 구현할 수 있는 자료형으로 어떤 선택적 기능을 제공한다는 사실을 선언하기 위해 쓰인다.
=> 예를 들면, Comparable은 어떤 클래스가 자기 객체를 다른 객체와의 비교 결과에 따른 순서를 갖는다고 선언할 때 쓰는 인터페이스이다.

이런 믹스인 기능을 추상클래스에 할 수 없다. 
=> 클래스가 가질 수 있는 상위 클래스는 하나 이기 때문에 좋은 방법이 아니다.

인터페이스는 여러 속성을 합쳐서 새로운 속성을 만들 수 있다.
=> singer와 SongWriter 속성을 합쳐서 새로운 인터페이스를 만들 수 있다.



1
2
3
4
5
6
7
8
9
10
public interface singer {
}
 
public interface SongWriter {
 
}
 
public interface SingerSongWriter extends Singer, SongWriter {
 
}
cs




하지만 이런 조합을 인터페이스가 아닌 클래스를 이용한다면 별도의 클래스를

계속 만들어 주어야 한다.

필요한 속성이 n개가 있을 때 조합의 가짓수는 2의 n승으로 이것을 조합 폭증이라고 한다.

추상 골격 구현 (Abstract skeletal implemetation)
해당 추상 골격 구현 클래스를 중요 인터페이스마다 두면, 인터페이스의 장점과 추상 클래스의 장점을 결합 할 수 있다.

방법
=> 인터페이스로는 자료형을 정의하고, 구현하는 일은 골격 구현 클래스에 맡기면 된다.

Ex) List



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 골격 구현 위에서 만들어진 완전한 List 구현
static List<Integer> intArrayAsList(final int[] a) {
  if (a == null
     throw new NullPointerExeception();
 
  return new AbstractList<Integer>() {
     public Integer get(int i) {
        return null;
     }
 
     @Oveerride
     public Integer set(int i, Integer val) {
         int oldVal = a[i];
         a[i] = val;
         return oldVal;
      }
 
      public int size() {
         return a.length;
      }
  };
}
cs



인터페이스 보다 추상 클래스가 좋은 경우는
새로운 기능이 추가되었을 때, 인터페이스는 새로운 메서드를 추가하더라도 새로 모두 구현해 주어야 하지만 추상클래스의 경우에는 그대로 하위클래스에서 사용할 수 있다는 것이다.
=> 하지만 Java 8에서 default 메소드를 통해 이 단점 또한 해결이 되었다.

또한 
public 인터페이스의 경우 공개되고 난 다음에는, 인터페이스 수정이 거의 불가능 하다. 그러므로 처음 설계부터 잘 구현해야 한다는 단점이 있다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙18 인용.



댓글()

모든 객체의 공통 메서드 - 규칙 8 equeals 재정의할 때는 일반 규악을 따르라

JAVA/Effective Java|2018. 5. 29. 07:46

Object  모든 객체 생성이 가능한 클래스이긴 하지만 기본적으로 계승해서 사용하도록 설계된 클래스 이다.

그런 Object 정의된 equals, hashCode, toString, clone, finalize 명시적인 일반 규약이 있다
 
재정의 하도록 설계된 메서드들이기 때문에 상황에 따라 재정의를 하지 않을 경우 HashMap, HashSet처럼 
 
해당 규약에 의존하는 클래스와 함께 사용하면 문제가 발생한다
 
 
 
 equals  정의 하지 않아도 되는 경우
 
이중 equals 메서드에 대해서 이야기 해보자.
 
Equals 재정의를 하였을  실패할 경우문제가 되기 때문에 아래와 같은 상황에서는 구태여 재정의 하지 않아도 된다.
 
1. 각각의 객체가 고유하다.
- Thread 같은 클래스는 Object equals 메소드를 그대로 사용해도 된다.
 
2. 구태여 equals 메소드를 사용할 필요가 없을 경우
 
3. 상위 클래스에서 정의한 equals 그대로 승계하여 사용하여도 무방한 경우
 
4. 클래스가 private, package-private 선언되어 있는경우, equals 메소드를 정의할 필요가 없다.
 
 
 
 equals 재정의  준수해야 하는 일반 규약
 
 
- null 아닌 참조 x 있을 , x.equals(x) true 반환한다.
- null 아닌 참조 x, y 있을 , x.equals(x), y.equals(x) true  true 반환
- null 아닌 참조 x, y, z 있을 , x.equals(y) true이고, y.equals(z) true이면 x.equals(z) true이다.
- null 아닌 참조 x 대해서, x.equals(null) 항상 false이다.
 
 
 
 좋은 equals 재정의  준수해야 하는 일반 규약
 
1. == 연산자를 사용하여 eqauals 인자가 본인과 같은지 확인하자
그렇게 true 반환하면 훨씬  속도가 빠르다.
 
2. instanceof 연산자를 통하여 인자의 자료형이 같은지 확인하라.
 
3. equals 사용될 인자를 정확한 자료형으로 캐스팅하라
- instanceof 연산자를 통해 정확하게 점검하였다면 캐스팅에 문제가 없을 것이다.
 
4. 자료형에 맞게 비교하라.
- float, double 이외의 기본 자료형은 == 연산자로 비교하고, float Float.compare 메서드를 double 필드는 Double.compare 메소드를 이용하여 비교하라. (float double필드는 Float.NaN, -0.0f 같은 상수가 있기 때문이다.)
 
5. NULL 허용되는 필드를 비교해야 할  NULL 포인트 에러를 피하라.
- (field == o.field || ( field != null & field.equlas(o.field)))
 
6. 성능을 최대한 끌어내기 위해서 다를 가능성이 가장 높거나 비교 비용이 낮은 필드부터 먼저 비교 한다.
 
 
출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙8 인용



댓글()