깔끔한 코드를 구현하기 위한 설계 규칙 네 가지
하이..
코드를 짜는 것보다 더 중요한 것이 있다.
바로 설계다.
우리는 항상 깔끔한 코드를 짤 생각은 하지만 그전에 설계를 제대로 하는 것을 잊곤 한다.
오늘은 착실하게 따르기만 하면 우수한 설계가 나오는 간단한 네 가지 규칙을 소개하고자 한다.
이규칙을 지키면 앞으로 여러분은 코드 구조와 설계를 파악하기 쉬워질 것이고,
SRP 나 DIP 같은 원칙을 적용하기도 쉬워질 것이고,
네가지 규칙이 우수한 설계의 창발성을 촉진할 것이다.
1. 모든 테스트를 실행하라.
2. 중복을 없애라.
3. 의도를 표현하라.
4. 클래스와 메서드 수를 최소로 줄여라, 단 실용적 관점에서 타협하라
하나씩 제대로 알아보자.
1. 모든 테스트를 실행하라.
위의 순서는 중요한 순서대로 나열한 것이다. 즉 모든 테스트를 실행하는 게 가장 중요하든 말이다.
모든 테스트 케이스를 항상 통과하는 시스템이어야 출시가 가능하다.
테스트가 불가능한 시스템은 검증을 할 수가 없다. 검증이 불가능한 시스템은 절대 출시하면 안된다.
이전 직장에 있을 때, 발생한 장애들 중 대다수는 테스트를 꼼꼼히 했다면 예방할 수 있던 장애들이 너무나 많았다. 검증이 불가능한 시스템을 출시한 댓가는 가혹하니 꼭 모든 테스트를 제대로 수행하자.
테스트가 가능한 시스템을 만들기 위해 노력하면 클래스는 크기가 작아지고 본래의 목적만 수행하는 클래스가 나올 것이고 이는 설계의 품질을 더욱더 높일 것이다.
결합도가 높은 프로그램은 테스트 케이스를 작성하기가 너무나 어렵다. 테스트케이스를 많이 작성하면 할수록 개발자는 DIP, 의존성주입, 인터페이스, 추상화 등의 도구로 결합도를 자연스레 낮추게 될 것이다.
테스트 케이스를 만들고 계속 돌리는 작업을 반복하면 낮은 결합도와 높은 응집력이라는 객체지향에 걸맞는 프로그램을 만들어 낼 수 있다.
2. 중복을 없애라.
중복을 없애는 것은 코드 리펙토링의 가장 기본적인 전략이다.
좋은 시스템을 만들기 위해서는 몇줄의 중복이라도 제거하기 위해 노력해야한다. 아래 예제 코드들은 "클린코드"라는 책을 참고하여 작성한 코드이다.
public void scaleToOneDimension (float desiredDimension, float) {
if(Math.abs(desiredDimension - imageDimension) < errorThreshould)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
Rendered0p newImage = ImageUtilities.getScaledImage(image, scalingFactor, scalingFactor);
image.dispose();
System.gc();
image = newImage;
}
public synchronized void rotate(int degrees) {
Rendered0p newImage = ImageUtilities.getRotatedImage(image, degrees);
image.dispose();
System.gc();
image = newImage;
}
위의 코드를 보면 바로 중복된 코드가 딱 보인다. 중복코드를 제거해보면 프로그래밍 해보면 아래와 같이 될것이다.
public void scaleToOneDimension (float desiredDimension, float) {
if(Math.abs(desiredDimension - imageDimension) < errorThreshould)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);
replaceImage(ImageUtilities.getScaledImage(Image, scalingFactor, scalingFactor));
}
public synchronized void rotate(int degrees) {
replaceImage(ImageUtilities.getScaledImage(Image, scalingFactor, scalingFactor));
}
private void replaceImage(Rendered0p newImage) {
image.dispose();
System.gc();
image = newImage;
}
코드의 중복을 지우고, 새 메서드를 뽑으니 코드가 쉽게 잘 읽히는 것을 알 수 있다. 다만 위의 코드는 문제가 있다. 바로 SRP를 위반한다는 것이다. 이때 새롭게 만든 메서드를 다른 클래스를 옮기면 SRP 도 지켜 개발을 할수가 있을 것이다.
그리고 중복을 제거하기 좋은 디자인 패턴이 있는데, 바로 템플릿 메서드 패턴이다.
템플릿 메서드 패턴은 고차원 중복을 제거할 목적으로 자주 사용되는 패턴이니 잘 익혀두도록하자.
https://devkingdom.tistory.com/301
3. 의도를 표현하라
남이 작성해놓은 코드를 볼때 정말 읽기 힘든 코드가 있다.
바로 의도가 정확하지 않은 프로그램이다.
보통 코드의 의도는 작성자는 굉장히 잘 알고 있다. 하지만 해당 코드를 유지보수하는 사람은?
장기적인 유지보수가 가능하게 코드를 만들기 위해서는 개발자의 의도를 분명히 표현해야한다. 그래야 코드가 이해하기 쉬워지고 이는 유지보수 비용을 줄이는 데 큰 역할을 한다.
의도를 표현하기위해서는 아래 다섯가지를 지켜야한다.
1) 좋은 이름을 선택해야한다.
- 메서드나 클래스. 변수의 이름만 봐도 무슨 기능을 하는지 알 수 있어야한다.
2) 메서드와 클래스의 크기를 가능한 줄인다.
- 작은 메서드와 클래스는 이름 짓기도 쉽고, 구현하기도 쉽고, 이해하기도 쉽다.
3) 표준명칭을 사용한다.
- 예를들어 디자인 패턴이 사용된다면 해당클래스에 해당 패턴의 이름을 넣어준다. 이는 설계의 의도에 대한 이해를 돕는다.
4) 단위 테스트 케이스를 꼼꼼히 작성한다.
- 잘 만든 테스트 케이스에는 클래스의 기능이 한눈에 보일 것이다.
5) 끊임 없이 의도를 표현하고자 노력하자.
- 당장의 완성물이 급하다고 아무렇게나 짜서는 안된다. 끝까지 노력하자.
4. 클래스와 메서드 수를 최소로 줄여라, 단 실용적 관점에서 타협하라
SRP 를 준수한다는 것은 중요하지만 너무 극단적인 방식은 오히려 득보다 실이 많아진다.
회사에서 코드를 작성하다보면 무의미하고 독단적인 회사의 코드 정책때문에 클래스 수나 메서드 수가 늘어날 때가 있다. 예를 들어 클래스마다 무조건 인터페이스를 생성하라고 요구하는 구현 표준이 대표적인 예다.
또 자료와 동작 클래스는 무조건 분리하라고 하는 개발자들도 있다.
이런 극단적인 방법들은 생산성을 떨어트린다.
하지만 좋은코드를 만드는 것은 생산성을 올리는 것이다. 실용적인 관점에서 타협하여 코드를 작성하자.
이상 글을 마치도록 하겠다. 다들 좋은 코드를 작성하길 바란다.
Ref.
로버트 C.마틴, 『클린코드』, 도서출판인사이트(2021), p216~222