본문 바로가기

TDD, CleanCode

Clean Code(로버트 마틴) - 코드 작성 메뉴얼

고등학생때 나는 밴드부였다. 그 당시 하나의 무대를 준비한다는 것은 그 무대가 끝이 나기 전까지 끝이 없는 것이라 생각했다. 더 나은 무대는 있어도, 완성된 무대는 없다는 생각에서이다. 대학생 때는 글을 썼다. 역시 완벽한 글은 없다고 생각해서 사소하지만 거대한 차이를 계속 고민했다.

이번 한달 간 우아한테크코스 프리코스동안 나는 주어진 과제를 완성 시켰다. 과제의 요구사항을 만족했고, 제대로 동작했다. 또한 요구사항을 통해서 꽤 많이 배웠다고 생각했다. 

그런데 Clean Code를 읽고 나는 오만했다는 것을 깨달았다. 무대도 글도 코드도, 더 나아질 수 있다. 하지만 완벽한 것은 없다. 하지만 완벽해 지기 위해 계속 노력해야한다.

 

이번 포스팅은 더 나은 코드를 위해 노력해야 할 메뉴얼들을 정리되어 있다. 이것 또한 완벽하지 않으므로 끊임 없이 배우고 수정해야 한다.

 

0. 기본 규칙

1. 한동안 아래의 네개의 규칙을 진리로 삼아 코딩하자. 

모든 테스트를 실행한다.

중복을 없앤다.

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

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

 

//p216 이후의 예제 p246이후의 예제를 곱씹어보자.

 

1. 변수 이름 작성하기

1. 의미 있게 구분하라.

○. 구분 짓지 못하는 변수명은 의미가 없다. 예를 들어 ProductInfo와 ProductData의 개념은 분리 되는가?

●. 또 변수 뒤에 타입을 작성하는 것도 의미가 없다면 좋지 않다. NameString과 Name이 다른 점이 무엇인가?

 

2. 한 개념에 한 단어만 사용하라.

○. Controller, Manager, driver와 같이 의미가 비슷한 단어들은 혼란을 유발한다. 어휘를 일관성있게 작성하라.

 

3. 의미 있는 맥락을 추가하라

○. 예를 들어 사용자의 주소를 나타내는 변수로 state라는 이름을 지었다면, state만 봐서는 이게 주소인지, 상태인지 의미를 유추하기 힘들다. 이 때, state와 함께 선언되는 다른 변수들의 이름이 주소와 연관된 단어라면 state의 옳은 뜻을 쉽게 유추할 수 있다.

 

2. 함수 작성하기

1. 작게 만들어라

○. 적어도 이 책에서 만큼은 크게 만들어서 좋을 것은 단 하나도 없다. 어떤 것도 최대한 작게 만들려고 하자.

if(isTestPage(pageData){
...
}

private Boolean isTestPage(Page pageData){
	return pageData.size > 0;
}

가정문 하나도 저렇게 쓰이니까 읽기도 좋고 테스트 하기도 쉬워진다. 그렇다고 만들기 어려운 것도 아니다.

 

2. 객체의 상태를 변경한 후 출력하는 함수를 만들지 마라.

객체의 변경은 객체 내부에서만 하는 것이다. 다른 객체에게 변경이나 생성을 맡기려 하지마라.

 

public class MovieRepository {
    private static List<Movie> movies = new ArrayList<>();

    static {
        Movie movie1 = new Movie(1, "생일", 8_000);
        movie1.addPlaySchedule(new PlaySchedule(createDateTime("2019-04-16 12:00"), 6));
        movie1.addPlaySchedule(new PlaySchedule(createDateTime("2019-04-16 14:40"), 6));
        ...이하 생략
        movies.add(movie1);
        }
        ​

영화 예매 목록을 담고 있는 객체이다.

Movie의 스케쥴을 추가하고 있는데, MovieRespository에서 추가하는 것이 아니라, Movie 내의 메소드에서 추가하고 있다.

public class Movie {
    private final int id;
    private final String name;
    private final int price;

    private List<PlaySchedule> playSchedules = new ArrayList<>();
    
    void addPlaySchedule(PlaySchedule playSchedule) {
        playSchedules.add(playSchedule);
    }
}​

엄연히 스케쥴을 관리해야 하는 것은 변수를 가지고 있는 Movie의 역할이다.

 

3. 함수는 하나의 일만 해야 한다.

부수 효과를 일으키지마라, 명령과 조회를 분리하라. 이 모든게 결국 함수가 하나의 일만 한다면 해결 될 일이다.

상태를 변경만 하면 변경만 하고 출력 할 것이라면 출력만 해라. 두 가지 모두는 오해를 일으키기도, 테스트 하기도 어렵다.

 

4. 반복하지 마라

예시 작성

 

5. 파라미터는 없거나 하나, 여튼 적은게 낫다.

클래스를 생성하여 최대한 줄이려고 노력하자

3. 주석 작성

1. 주석은 작성 하지 않는 것이 가장 좋다

코드로 모든 것을 설명 하도록 노력하자

2. Todo 주석을 작성하자

일을 마치고 오늘 어디 까지 했고, 내일 무엇을 하면 좋을지 기록하자.

 

 

4. 객체 작성

1. 자료를 추상화 하자.

추상 인터페이스를 이용하여 클래스를 생성하자. 관련이 적은 클래스들도 하나로 묶어 사용 할 수 있다.

아래는 인터페이스와 추상 클래스에 관한 내용을 요약한 것인데, 사용법이라던지 쉬운 예제로 정리 되어 있다.

출처로 가서 예제로 익히자.

인터페이스 추상클래스
클래스가 아니다 클래스다
클래스와 관련이 없다 클래스와 관련이 있다
(주로 베이스 클래스로 사용)
한 개의 클래스에 여러 개를 사용할 수 있다 한 개의 클래스에 여러 개를 사용할 수 없다.
구현 객체의 같은 동작을 보장하기 위한 목적 상속을 받아서 기능을 확장시키는 데 목적

출처 : https://loustler.io/languages/oop_interface_and_abstract_class/ 

 

2. 객체의 구조를 숨겨라

단지 private하게 변수를 선언 한다고, 구조를 숨기는 것이 아니다. 객체는 내부에서 값을 얻고 변화하고 계산하고, 외부로 나오는 메소드는 그 결과값만 리턴하는 것. 이외의 다른 메소드들은 피하자.(절때, 객체 통체로 꺼내서 get메소드를 이용하지 마라 방법이 없지 않다. 어떻게든 방법을 만들어 내라. 절때다. 절때)

 

5. 오류 처리

1. 가정문 보단 try-catch문

2. try-catch문도 가능하면 사용하지 않는 경우를 고민하자

try{
	MealExpenses expenses = expenseReportDAO.getMeals(employee.getId());
    total += expenses.getTotal();
} catch(MealExpensesNotFound e){
	total += getMealPerDiem();
} // 직원의 id로 타입에 따라 결제방식이 다르다.

public class PerDiemMealExpenses implements MealExpenses{
	public int getTotal{
    // 기본값으로 일일 기본 식비를 반환한다
    }
}//p138

인터페이스를 이용하여, 조건에 따라 반환하는 값이 다른 MealExpenses를 생성하여 문제를 해결했다.

 

3.Null을 반환하지마라.

빈 배열이나 0과 같은 값을 반환해라. 나중에 null로 인한 NullPointerException의 발생을 막을 수 있다.

 

6. 단위 테스트 하기

1. 제대로 하는 법 부터 배우자.

작게 그리고 가능 한 모든 함수를 테스트 하자

 

7. 클래스 생성하기

1. 클래스 역시 작아야 한다.

어느 정도 작게냐면, 딱 하나의 책임만 하는 클래스를 생성 해보려고 하자(단어 하나로 클래스의 모든 것을 표현 할 수 있을 크기)

 

2. 단일 책임 원칙에 따른다.

위와 비슷하다. 클래스는 변경할 이유가 단 하나여야 한다. 예를 들어 버전을 관리하고, 때론 관리된 버전을 출력하는 클래스가 있다면, 이것이 수정 되야 한다면 책임이 두가지기 때문에 두가지 이유로 수정된다.

 

3. 인스턴스 변수의 수가 작아야한다.