본문 바로가기
Book

[독서 기록] 클린 코드 11장, 12장 , 13장 외 / 시스템, 창발성, 동시성

by Renechoi 2022. 11. 13.

클린 코드, 로버트 C. 마틴, 박재호 이해영 옮김, 인사이트

 
Clean Code(클린 코드)
『Clean Code(클린 코드)』은 오브젝트 멘토(Object Mentor)의 동료들과 힘을 모아 ‘개발하며’ 클린 코드를 만드는 최상의 애자일 기법을 소개하고 있다. 소프트웨어 장인 정신의 가치를 심어 주며 프로그래밍 실력을 높여줄 것이다. 여러분이 노력만 한다면. 어떤 노력이 필요하냐고? 코드를 읽어야 한다. 아주 많은 코드를. 그리고 코드를 읽으면서 그 코드의 무엇이 옳은지, 그른지 생각도 해야 한다. 좀 더 중요하게는 전문가로서 자신이 지니는 가치와 장인으로서 자기 작품에 대한 헌신을 돌아보게 된다.
저자
로버트 C 마틴
출판
인사이트
출판일
2013.12.24

11장 시스템

 

 

도시가 돌아가는 또 다른 이유는 적절한 추상화와 모듈화 때문이다. 그래서 큰 그림을을 이해하지 못할지라도 개인과 개인이 관리하는 '구성요소'는 효율적으로 돌아간다.

- 194p 

 

 

시스템 제작과 시스템 사용을 분리하라 

- 194p 

 

 

대다수 애플리케이션은 시작 단계라는 관심사를 분리하지 않는다. 준비 과정 코드를 주먹구구식으로 구현할 뿐만 아니라 런타임 로직과 마구 뒤섞는다. 다음이 전형적인 예다.

 

    public Service getService() {    
        if (service == null) {  
            service = new MyServiceImpl(...); // 모든 상황에 적합한 기본값일까?       
        } 
        return service;      
    }

이것이 초기화 지연 혹은 계산 지연이라는 기법이다. 장점은... 실제로 필요할 때까지 객체를 생성하지 않으므로 불필요한 부하가 걸리지 않는다. ... 둘째 어떤 경우에도 null 포인터를 반환하지 않는다. 

 

하지만 getService 메서드가 myServiceImpl과 생성자 인수에 명시적으로 의존한다. 런타임 로직에서 myServiceImple 객체를 전혀 사용하지 않더라도 의존성을 해결하지 않으면 컴파일이 안 된다.

 

- 195p

 

테스트 문제

- 195p 

 

무엇보다 myServiceImpl이 모든 상황에 적합한 객체인지 모른다는 사실이 가장 큰 우려다. 

- 195p

 

 

Main 분리

시스템 생성과 시스템 사용을 분리하는 한 가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정한다.

- 196p

 

 

팩토리

- 197p

 

 

의존성 주입

사용과 제작을 분리하는 강력한 메커니즘 하나가 의존성 주입이다. 의존성 주입은 제어 역전 기법을 의존성 관리에 적용한 메커니즘이다. 제어 역전에서는 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 떠넘긴다. 새로운 객체는 넘겨받은 책임만 맡으므로 단일 책임을 지키게 된다.

- 198p

 

 

자바프록시

AOP

AspectJ

- 202 ~ 209p 

 

 

처음부터 올바르게 시스템을 만들 수 있다는 믿음은 미신이다. 오늘은 주어진 사용자 스토리에 맞춰 시스템을 구현해야 한다. 내일은 새로운 스토리에 맞춰 시스템을 조정하고 확장하면 된다. 이것이 반복적이고 점진적인 애자일 방식의 핵심이다. TDD, 리팩토링, 깨끗한 코드 수준에서 시스템을 조정하고 확장하기 쉽게 만든다.

- 199p

 

의사 결정을 최적화하라

모듈을 나누고 관심사를 분리하면 지엽적인 관리와 결정이 가능해진다. 도시든 소프트웨어든 프로젝트든, 아주 큰 시스템에서는 한 사람이 모든 결정을 내리기 어렵다.

 

가장 적합한 사람에게 책임을 맡기면 가장 좋다. 우리는 때때로 가능한 마지막 순간까지 결정을 미루는 방법이 최선이라는 사실을 까먹곤한다. 게으름하거나 무책임해서가 아니다. 최대한 정보를 모아 최선의 결정을 내리기 위해서다. 

- 211p

 

 


12장 창발성

 

켄트 벡은 다음 규칙을 따르면 설계는 '단순하다'고 말한다.

- 모든 테스트를 실행한다.

- 중복을 없앤다

- 프로그래머 의도를 표현한다

- 클래스와 메서드 수를 최소로 줄인다.

 

위 목록은 중요도 순이다.

 

- 216p

 

 

Template Method 패턴

 

중복이 존재하는 코드

public class VacationPolicy {
  public void accrueUSDDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
    // ...
    // 휴가 일수가 미국 최소 법정 일수를 만족하는지 확인하는 코드
    // ...
    // 휴가 일수를 급여 대장에 적용하는 코드
    // ...
  }
  
  public void accrueEUDivisionVacation() {
    // 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
    // ...
    // 휴가 일수가 유럽연합 최소 법정 일수를 만족하는지 확인하는 코드
    // ...
    // 휴가 일수를 급여 대장에 적용하는 코드
    // ...
  }
}

 

TEMPLATE METHOD 패턴을 적용해 눈에 들어오는 중복을 제거한다. 

abstract public class VacationPolicy {
  
  public void accrueVacation() {
    caculateBseVacationHours();
    alterForLegalMinimums();
    applyToPayroll();
  }
  
  private void calculateBaseVacationHours() { /* ... */ };
  abstract protected void alterForLegalMinimums();
  private void applyToPayroll() { /* ... */ };
}

public class USVacationPolicy extends VacationPolicy {
  @Override protected void alterForLegalMinimums() {
    // 미국 최소 법정 일수를 사용한다.
  }
}

public class EUVacationPolicy extends VacationPolicy {
  @Override protected void alterForLegalMinimums() {
    // 유럽연합 최소 법정 일수를 사용한다.
  }
}

- 219 ~ 220p

 


 

13장 동시성 

 

 

동시성과 깔끔한 코드는 양립하기 어렵다.

- 226p 

 

 

무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다. 

- 226p 

 

 

동시성 방어원칙 

- SRP

- 자료 범위를 제한하라

- 자료 사본을 사용하라

- 스레드는 가능한 독립적으로 구현하라

 

- 230 ~ 232p 

 


 

기타 책의 뒷 부분 

 

 

부적절한 static 함수

Math.max(double a, double b)는 좋은 static 메서드다. 특정 인스턴스와 관련된 기능이 아니다. new Math().max(a,b)나 a.max(b)라 하면 오히려 우습다. max 메서드가 사용하는 정보는 두 인수가 전부다. 메서드를 소유하는 객체에서 가져오는 정보가 아니다. 결정적으로 Math.max 메서드를 재정의할 가능성은 거의 아니 전혀 없다. 

 

그런데 간혹 우리는 static으로 정의하면 안 되는 함수를 static으로 정의한다. 다음 예를 살펴보자.

 

HourlyPayCalculator.calculatePay(employee, overtimeRate);

언뜻 보면 static 함수로 여겨도 적당하다. 특정 객체와 관련이 없으면서 모든 정보를 인수에서 가져오니까. 하지만 함수를 재정의할 가능성이 존재한다. 수당을 계산하는 알고리즘이 여러 개일지도 모른다. 예를 들어, OvertimeHourlyPayCalculator와 StraightTimeHourlyPayCalculator를 분리하고 싶을지도 모른다. 그러므로 위 함수는 static 함수로 정의하면 안 된다. Employee 클래스에 속하는 인스턴스 함수여야 한다. 

 

- 382p 

 

 

조건을 캡슐화하라

부울 논리는 (if나 while문에다 넣어 생각하지 않아도) 이해하기 어렵다. 조건의 의도를 분명히 밝히는 함수로 표현하라. 예를 들어,

if (shouldBeDeleted(timer))

 라는 코드는 다음 코드보다 좋다.

if (timer.hasExpired() && !timer.isRecurrent())

- 388p

 

 

부정 조건은 피하라

부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하면 긍정 조건으로 표현한다. 예를 들면,

if (buffer.shouldCompact())

라는 코드가 아래 코드보다 좋다.

if (!buffer.shouldNotCompact())

- 389p

 

 

 

숨겨진 시간적인 결합

때로는 시간적인 결합이 필요하다. 하지만 시간적인 결합을 숨겨서는 안 된다. 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다. 다음 코드를 살펴보자.

 

public class MoogDiver {
	Gradient gradient;
    List<Spline> splines;
    
    public void dive(String reason)
    	saturateGradient();
        reticulateSplines();
        diveForMoog(reason);
    }

위 코드에서 세 함수가 실행되는 순서가 중요하다. 먼저 gradient를 처리하기 위해 saturateGradient()를 호출하고 나서, 다시 splines를 처리하기 위해 reticulateSplines()를 호출하고, 마지막으로 diveForMoog()를 수행해야 한다. 불행히도 위 코드는 이런 시간적인 결합을 강제하지 않는다. 프로그래머가 reticulateSplines를 먼저 호출하고 saturateGradient를 다음으로 호출하는 바람에 UnsaturatedGradientException 오류가 발생하도 막을 도리가 없다. 다음 코드가 더 좋다.

 

public class MoogDiver {
	Gradient gradient;
    List<Spline> splines;
    
    public void dive(String reason)
    	Gradient gradient = saturateGradient();
        List<Spline> splines = reticulateSplines(gradient);
        diveForMoog(splines, reason);
    }

위 코드는 일종의 연결 소자를 생성해 시간적인 결합을 노출한다. 각 함수가 내놓는 결과는 다음 함수에 필요하다. 그러므로 순서를 바꿔 호출할 수가 없다.

 

함수가 복잡해진다고 불평할지도 모르겠다. 맞는 말이다. 하지만 의도적으로 추가한 구문적인 복잡성이 원래 있던 시간적인 복잡성을 드러낸 셈이다. 

 

- 390 ~ 391p 

 

 

상수 대 enum

다음 코드가 좋은 예다. 

 

public class HourlyEmnployee extends Employee {
	private int tenthsWorked;
    HourlyPayGrade grade;
    
    public Money calculatePay() {
    	int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
        int overTime = thenthsWokred - straightTime;
        return new Money(
        	grade.rate() * (tenthWorked + OVERTIME_RATE * overTime)
        );
    }
public enum HourlyPayGrade {
	APPRENTICE {
    	public double rate() {
    		return 1.0;
        }
    },
    .
    .
    .
    public abstract double rate();
  }

 

반응형