Blog
컴퓨터 공학
소프트웨어 아키텍쳐
클린 코드

Clean Code

2장 의미 있는 이름

  1. 의도를 분명히 밝혀라

  2. 그릇된 정보를 피하라

실제로 담긴 내용이 아닌데 그런 내용을 담고 있는 변수명을 사용하면 안된다.

  1. 의미있게 구분하라

  2. 발음하기 쉬운 이름을 사용하라

  3. 검색하기 쉬운 이름을 사용하라

  4. 인코딩을 피하라

  5. 자신의 기억력을 자랑하지 마라

  6. 한 개념에 한 단어를 사용하라

이를 테면 내가 이전 프로젝트에서 db에서 데이터를 불러오는데 get과 fetch를 사용한게 해당됨

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

  2. 불필요한 맥락을 없애라

주소라는 클래스에 state라는 변수가 갑자기 있으면 주소의 state인지, 상태에 해당하는 state인지 알기 어렵다. 예를 들어서 Trend라는 Repository가 있고 Transaction이라는 Repository가 있을 때, 둘 다 get()이라는 함수를 사용하게 되면 Transaction.get(), Trend.get()이라고 사용하면 둘의 차이를 알기 쉽다. 그런데 Trend라는 Repository에 trendGet()이라는 함수를 사용하게 되면 Trend.trendGet()이라고 사용하게 되면 의미없는 반복을 하게 되며 이를 좋은 코드로 보기는 어렵다.

타입별 이름

  • 클래스 이름 : 명사나 명사구
  • 함수 이름 : 동사나 동사구

3장 함수

  1. 작게 만들어라

if문 else문 while문 등에 들어가는 블록은 한 줄이어야 한다. 거기에 들어가는 로직을 함수로 짠다.

  1. 한 가지만 해라

  2. 함수 당 추상화 수준은 하나로

내려가기 규칙 : 추상화 수준이 한 단계 낮은 함수가 아래에 온다.

  1. Switch문

다형성을 이용해라.

  1. 서술적인 이름을 사용하라

함수가 작고 단순할수록 서술적인 이름을 고르기도 쉬워진다.
길고 서술적인 이름이 짧고 어려운 이름보다 좋다.
이름을 정하느라 시간을 들여도 괜찮다.

  1. 함수 인수

함수에서 이상적인 인수 개수는 0개(무항)다. 다음은 1개(단항)고, 다음은 2개(이항)다.
인스턴스 변수로 선언하는 대신 인수로 전달을 했다면 그 인수를 마주칠 때마다 그 인수를 해석하는 과정을 다시 반복해야 한다.
플래그 인수는 추하다. 함수로 부울 값을 넘기는 행위는 함수가 한 ㄲ버ㅓㄴ에 여러 가지를 처리한다고 대놓고 공표하는 셈이다.

  1. 부수 효과를 일으키지 마라

함수명과 다른 동작을 하는지 계속 생각해보기.

  1. 동사와 키워드

함수와 인수가 동사/명사 쌍을 이뤄야 한다.
예를 들어서 assertEquals보다 assertExpectedEqualsActual(expected, actual)이 더 좋다.

  1. 출력 인수

예를 들어서 List에서 필터를 걸어서 필터에 해당하는 List만 부분내거나 아니면 그 List가 비어있는지 확인하는 함수가 있다면 해당 함수에 들어간 인수는 입력 인수다. 그러나

List<String> addTest(List<String> list) {
  list.add("test");
  return list;
}

이와 같은 함수에 들어간 인수는 출력인수다. 일반적으로 이런 출력 인수는 피해야 한다. 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택해야 한다.

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

오류가 발생할 가능성을 if문으로 test를 하게 되면 코드가 복잡해지고 가독성이 떨어진다. try catch 문을 사용하고 try catch문의 블록을 함수로 분리하자.

  1. 구조적 프로그래밍

루프 안에서 break나 continue를 사용해선 안된다.

  1. 함수를 짜는 방법

소프트웨어를 짜는 행위는 여느 글짓기와 비슷하다. 논문이나 기사를 작성할 때는 먼저 생각을 기록한 후 읽기 좋게 다듬는다. 함수를 짜는 행위도 이와 다르지 않다. 일단 나오는대로 짜고 리팩토링을 하라는 얘기인 것 같다.

추상화 수준

  • cody yun :
    자동차를 만드는걸 추상화 수준으로 비교해보면 ”엔진을 만든다 > 4기통 엔진을 만든다 > 4기통 2000cc엔진을 만든다 > 4기통 2000cc GDI엔진을 만든다“ 로 추상화 수준을 나눌 수 있어요
    하위 문제와 상위 문제로 나누는 개념이 추상화는 아니며 세부적인 것이 아니라 구체적인 것이다.

4장 주석

주석은 쓰레기다.

6장 객체와 자료구조

캡슐화

예를 들어서 BankAccount라는 객체가 있다고 가정할 때, balance라는 변수에 접근하고 balance의 값을 바꿀 수 있게 된다면 이는 현실 세계에서는 큰 일 일것이다. 이처럼 객체 내부에서 변수들을 private으로 선언해놓고 필요에 따라 gettersetter를 이용해야 한다.

class BankAccount {
    public double balance;
}

Example

class Point {
    public double x;
    public double y;
}

자료/객체 비대칭

Geometry 클래스를 절차지형적으로 구현을 했을 때는 함수를 하나 더 추가하고 싶다면 (예를 들어서 둘레의 길이를 구하고 싶다면) Geometry 클래스에 함수를 하나 더 추가해주면 되지만 절차지향에서는 interface에 함수 원형을 넣고 해당 interface를 implements하는 전체 클래스를 전부 수정해야 한다. 필자가 하고 싶은 말은 절차지향을 쓸 상황과 객체지향을 쓸 상황을 구분해서 쓰는 것이 중요하다는 것이다.

새로운 자료 타입이 필요할 경우 > 객체 지향
새로운 함수가 필요할 경우 > 절차 지향

class Square {
    public Point topLeft;
    public double side;
}
class Rectangle {
    public Point topLeft;
    public double height;
    public double width;
}
class Circle {
    public Point center;
    public double radius;
}
class NoSuchShapeException extends Exception {
    public NoSuchShapeException() {
        super("Shape type is not recognized.");
    }
}
class Geometry {
    public final double PI = 3.141592653589793;
 
 
    public double area(Object shape) throws NoSuchShapeException {
        if (shape instanceof Square) {
            Square s = (Square) shape;
            return s.side * s.side;
        } else if (shape instanceof Rectangle) {
            Rectangle r = (Rectangle) shape;
            return r.height * r.width;
        } else if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            return PI * c.radius * c.radius;
        }
        throw new NoSuchShapeException();
    }
}

디미터 법칙

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.

즉, 객체는 조회함수로 내부 구조를 공개하면 안된다는 것이다. 여기서 말하는 조회함수란 getter나 setter 처럼 내부 구조(변수)를 출력해주는 함수를 말한다.

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        String status = car.getEngine().getStatus();
    }
}
 
class Car {
    private Engine engine;
 
    public Engine getEngine() {
        return engine;
    }
}
 
class Engine {
    private String status;
 
    public String getStatus() {
        return status;
    }
}

위와 같은 코드가 있다고 가정할 때, Engine의 stutus를 참조하기 위해서 Car 내부에 Engine이라는 객체가 있다는 내부 사정을 알게 되므로 이는 디미터 법칙에 위반된다.
따라서 위의 코드는 아래와 같이 수정해야 한다.

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        String status = car.getEngineStatus();
    }
}
 
class Car {
    private Engine engine;
 
    public String getEngineStatus() {
        return engine.getStatus();
    }
}
 
class Engine {
    private String status;
 
    public String getStatus() {
        return status;
    }
}

train wreck

다음과 같은 코드를 기차 충돌(train wreck)이라고 한다.

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

위 코드는 아래와 같이 쓰는게 좋다.

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

하지만 위의 두 코드 모두 한 함수에서 여러 ctxt, opts 등 여러 객체를 참조하고 있는 것이라면 이는 디미터 법칙 위반이다.
임시 디렉터리의 위치를 얻어와서 위임할려는 함수에게 전달하는 것이 아니라 ctxt에게 위임하라고 해야한다.

자료 전달 객체

공개 변수만 있고 함수가 없는 클래스를 자료 전달 객체(DTO, data transfer object)라고 한다.

10장 클래스

SRT(Single Responsibility Principle)

단일책임원칙은 클래스가 변경되어야 하는 이유는 오직 하나뿐이어야 한다는 원칙이다.

응집도

클래스는 인스턴스수가 작아야 한다. 인스턴스를 활용하는 메서드가 많은 클래스일수록 응집도가 높은 클래스라고 할 수 있다.

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

Reference

리핵님 블로그 (opens in a new tab)
Code with Andrea (opens in a new tab)
Github 1 (opens in a new tab)
Github 2 (opens in a new tab)