본문 바로가기
👩‍💻TIL/기술면접

클린코드와 코드 리팩토링

by devuna 2020. 2. 13.
728x90

클린코드와 리팩토링

 Clean Code(클린 코드)-로버트 C. 마틴

“컴퓨터가 이해할수 있는 코드는 어느 바보나 다 짤 수 있다.
좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.”

Martin Fowler

프로젝트를 하게되면 여러 명의 개발자가 투입되어 소스를 공유하며 코드 작업을 한다.

그리고 프로젝트가 완성되면 유지보수를 하게 될 개발자가 쉽고 빠르게 코드분석을 할 수 있도록 해야한다.

따라서 개발자에게 있어 클린코드와 리팩토링은 필수 사항이다.

📌그래서, 클린코드란?

클린코드는 가독성이 높은 코드를 말한다.

가독성을 높이려면 다음과 같이 구현해야 한다.

  • 네이밍이 잘 되어야 함
  • 오류가 없어야 함
  • 중복이 없어야 함
  • 의존성을 최대한 줄여야 함
  • 클래스 혹은 메소드가 한가지 일만 처리해야 함

얼마나 코드가 잘 읽히는 지, 코드가 지저분하지 않고 정리된 코드인지를 나타내는 것이 바로 '클린 코드'

public int AAA(int a, int b){
    return a+b;
}public int BBB(int a, int b){
    return a-b;
}

위의 코드에는 두 가지 문제점이 있다.

첫째는 함수 네이밍이다. 다른 사람들이 봐도 무슨 역할을 하는 함수인 지 알 수 있는 이름을 사용해야 한다.

둘째는 함수와 함수 사이의 간격이다. 여러 함수가 존재할 때 간격을 나누지 않으면 시작과 끝을 구분하는 것이 매우 힘들다.

 

개선한 것이 아래의 예시이다.

public int sum(int a, int b){
    return a+b;
}

public int sub(int a, int b){
    return a-b;
}

💡 클린코드 구현방법

Ⅰ. 변수 이름 작성하기

1. 의미 있게 구분하라.

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

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

 

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

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

 

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

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

 

Ⅱ. 함수 작성하기

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. 파라미터는 없거나 하나, 적은게 낫다.

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

 

Ⅲ. 주석 작성하기

 

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

최대한 코드만 보고 이해할 수 있도록 코드를 구현하자

 

2. Todo 주석을 작성하자

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

 

Ⅳ. 객체 만들기

1. 자료를 추상화 하자.

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

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

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

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

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

 

2. 객체의 구조를 숨겨라

단지 private하게 변수를 선언 한다고, 구조를 숨기는 것이 아니다. 객체는 내부에서 값을 얻고 변화하고 계산하고, 외부로 나오는 메소드는 그 결과값만 리턴하는 것. 이외의 다른 메소드들은 피하자.

(절대로, 객체 통체로 꺼내서 get메소드를 이용하지 마라 방법이 없지 않다. 어떻게든 방법을 만들어 내라. 절대)

 

Ⅴ. 오류 처리

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의 발생을 막을 수 있다.

Ⅵ. 단위 테스트 하기

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

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

Ⅶ. 클래스 생성하기

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

어느 정도 작게냐면, 딱 하나의 책임만 하는 클래스를 생성 해보려고 하자

(단어 하나로 클래스의 모든 것을 표현 할 수 있을 크기가 좋다)

 

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

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

 

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

📌 리팩토링이란?

프로그램의 외부 동작은 그대로 둔 채, 내부의 코드를 정리하면서 개선하는 것을 말함

이미 공사가 끝난 집이지만, 더 튼튼하고 멋진 집을 만들기 위해 내부 구조를 개선하는 리모델링 작업

프로젝트가 끝나면, 지저분한 코드를 볼 때 가독성이 떨어지는 부분이 존재한다.

이 부분을 개선시키기 위해 필요한 것이 바로 '리팩토링 기법'

리팩토링 작업은 코드의 가독성을 높이고, 향후 이루어질 유지보수에 큰 도움이 된다.


리팩토링이 필요한 코드는?

  • 중복 코드
  • 긴 메소드
  • 거대한 클래스
  • Switch 문
  • 절차지향으로 구현한 코드

리팩토링의 목적은, 소프트웨어를 더 이해하기 쉽고 수정하기 쉽게 만드는 것

리팩토링은 성능을 최적화시키는 것이 아니다. 코드를 신속하게 개발할 수 있게 만들어주고, 코드 품질을 좋게 만들어준다. 

코드가 이해하기 쉽고, 수정하기 쉽다면? → 개발 속도가 증가!


리팩토링이 필요한 상황

소프트웨어에 새로운 기능을 추가해야 할 때

명심해야할 것은, 우선 코드가 제대로 돌아가야 한다는 것. 리팩토링은 우선적으로 해야 할 일이 아님을 명심하자

객체지향 특징을 살리려면, switch-case 문을 적게 사용해야 함(switch문은 오버라이드로 다 바꿔버리자)

 

💡클린코드와 리팩토링의 차이?

리팩토링이 더 넓은 의미를 가진다.

클린 코드는 단순히 가독성을 높이기 위한 작업으로 이루어져 있다면,

리팩토링은 클린 코드를 포함한 유지보수를 위한 코드 개선이 이루어진다.

클린코드와 같은 부분은 설계부터 잘 이루어져 있는 것이 중요하고,

리팩토링은 결과물이 나온 이후 수정이나 추가 작업이 진행될 때 개선해나가는 것이 올바른 방향이다.

 

출처

https://moonsupport.tistory.com/188?category=797514

https://github.com/gyoogle/tech-interview-for-developer/blob/master/Computer%20Science/Software%20Engineering/Clean%20Code%20%26%20Refactoring.md

728x90

댓글