Now Loading ...
-
Clean Code 후기
1장 [깨끗한 코드]
회사가 망한 원인은 바로 나쁜 코드 탓..
나쁜 코드는 개발 속도를 크게 떨어트린다. → 현재 기반을 잡는 상태이기에 여러 상황에 따라 유연히 대체 가능한 코드를 구현해야한다.
나쁜 코드의 위험을 이해하지 못하는 관리자 말을 그대로 따르는 행동은 전문가답지 못하다 → 관리자 측의 압박에 무너지지 말고 나의 기준이 있어야 된다.
그러면 좋은 코드란?
check list: 우아한, 효율적, 논리가 간단, 의존성 줄이기, 성능 최적, 깔끔한 오류 처리, 한가지마 집중, 가독성, 다른사람이 고치기 쉽게, 테스트 케이스, 중복이 없음, 짐작대로 기능이 수행되게
우리가 항상 모든 코드에 있어 체크리스트를 놓치는 부분이 있을 수 있다. 항상 코드를 깨끗하게 유지하는 것(정리)을 놓치지 말자
2장 [의미있는 이름]
클린 코드는 복수형인 경우 s를 붙이거나, 변수명에 복수를 의미하는 명을 추가 ex) Accounts, accountGroup
List에 있어 단어는 특수하다. → 우리들은 어떻게 정의를 내리면 좋을까?
흡사한 이름에 대한 주의! – ex) 현재 User 기능 중, User 대해 정보를 가져올 때, 두 방식으로 가져올 수 있는데(security, db) 이에 이름을 짓는 것이 고민
하나의 데이터를 계속 가공하다보면 불용어(AccountData, AccountInfo) 에 대한 유혹이 많아지는데, 이 함정에 주의하자
각 자바 표현에 대한 명명
클래스 이름 = 명사, 명사구
메서드 이름 = 동사, 동사구
생성자 중복정의 시 정적 팩토리 메서드 사용 ( 메서드 함수명에 의미가 담겨있다)
이름을 지을 때 개발적 관점에서 짓는 것에 동의한다.
좋은 이름에 대한 선택은 모두가 생각하는 것에 따라 순위가 다를 수 있다.
또 이름을 바꾸는 것에 반대가 두렵기도 하고, 내가 신중히 고민하여 작명한 이름이 거절당할때의 허탈함이 있을 수 있다.
이 책에서 저자는 오히려 좋은 이름으로 바꾼다 하면 ‘반갑고 고맙다 하는 마인드’를 따라 가는 것이 더 나아질 수 있는 덕목이라 생각하여 스스로 유연해질 수 있도록 노력해야겠다.
3장 [함수]
함수 규칙은 ‘작게->작게’ 근데 제일 좋은 작게 어느정도? (클린코드는 들여쓰기 2단 넘으면 x)
한가지 일만하는 것, 그 일을 잘하는것, thumbs up (Util 성처럼 사용해야하나?)
Switch 문에 대해 추상 팩토리에 숨겨야 한다?
함수로 상태를 변경해야 할 때는 함수가 속한 객체 상태 변경을 택해라
try catch 같은 경우, 하나의 함수로 만들어서 따로 빼는 것으로 만들어 exception 처리 관리를 쉽게하자
중복제거를 위해 코드를 최대한 부모 클래스에 몰아 넣는다.
함수짜는 법 → 줄줄이 나열 → 다듬기 → 이름바꾸기 → 쪼개기 → 좋은 함수
4장 [주석]
함수를 만들 때마다 주석을 달았던 기억이 났다.
책에서의 저자는 주석에 대해 좋게 바라보지 않는다. but 나는 주석 자체를 나쁘게 보지 않는다. 함수가 바뀌면서 주석이 사라지는 것은, 그만큼 좋은 코드로 바뀌었다는 것이고, 허나 저자와 나의 공통점은 주석을 어떻게 적냐는 것이다.
최대한 함수의 서술적 방식을 이용해 주석에 대한 기능을 대체할 수 있도록 해라.
내가 생각하는 주석을 넣어야 하는 부분이라면, 코드 내에 오류가 날 수 있거나, TODO 부분, 패턴에 대한 설명 등이다. 그리고 클래스 파일을 열었을 때, 코드 중에서 꼭 한번 확인해보고 소스를 읽어야 하는 것을 기준으로 작성하는 것이 좋다 생각한다.
5장 [형식맞추기]
이번 장 같은 경우는 저희는 IDE 툴이나 여러 auto complete 기능들을 통해 잘 지켜진다 여겨지고 있으며,
개인적인 느낌으로는 저자측의 사례는 옛날 코딩에 대한 사례를 예시로 들어 좀 더 강조하려는 느낌이 들긴 한다.
종속 함수나 개념적 유사성에 대한 내용들만 잘 지킬 수 있다면, 형식 맞추기에 있어서 큰 문제가 되지 않는다 생각한다.
6장 [객체와 자료 구조]
추상 인터페이스를 제공 해사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스이다.
디미터 법칙 : 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.
기차충돌은 조잡하다 → 디미터의 법칙에 어긋난다. → 그럼 한번만 부르는 상황이여도 매번 나눠야 하나?
그럼 기차충돌을 묶어놓은 하나의 함수를 만드는것?
DTO: 가공되지 않은 정보를 어플리케이션 코드에 사용할 객체로 변환하는 구조체.
활성 레코드에 비즈니스 규칙 메서드 추가는 바람직하지 않음
7장 [오류 처리]
오류코드보다 예외 사용
void send(){
try{
sendMail();
}catch(MailException e){
log.error(e);
}
}
void sendMail() throws MailException{
...
connectStibee();
...
disconnect();
}
void connectStibee(){
...
}
void connectStibee(){
...
if(connectFail){
throw new MailException("메일 전송 실패");
}
}
캡슐화하여 나타내면 연결 해제하는 로직과 오류를 처리하는 로직을 분리(독립적)
Try Catch Finally
try 안에서 무슨일이 생기든 catch 는 일관성있게 유지
그래서 작성하다 try catch문이 나오는 상황이 오면, 일단은 try catch finally문을 먼저 작성해놓고 코드를 작성해보자
작성 후 리팩토링 과정을 거치며 논리를 추가하는 방식을 구현해보자 (트랜잭션의 본질 유지를 위해)
미확인 예외를 사용하라
확인된 오류가 치르는 비용에 상응하는 이익을 제공하느냐 : 캡슐화를 하면서 catch 블록을 여러개 올릴 때가 있는데, 이때 하위 코드를 변경 시 상위 코드에 catch를 추가 또는 throw 절을 모두 추가하는 즉, 전부 고쳐야 할 때가 있다. 이런 일은 대체로 외부의 라이브러리를 사용할 때 나타난다.
예외에 의미를 제공하라
오류메시지에 정보를 담아서 예외를 함께 던져라
호출자를 고려해 예외 클래스를 정의하라
우리는 오류가 발생할 때, 오류를 잡아내는 방법을 중요시 생각해야한다 말한다. 전 회사에서 작업했을 때, 공공기관에서 취약점에 의해 모든 오류에 대한 exception을 catch 해야해서 각 try catch마다 catch문을 계속 붙였던 기억이 난다. 그때는 막무가내로 붙였는데, 책은 wrapper를 통해 감싸서 던지라고 제안한다. 이부분에 대해 좋다 생각한다. 책에서 LocalPort 같은 내용을 예시로 들었는데, PortDeviceFailure로 묶어서 하나로 바꾼 방식은 open 함수에 대한 모든 Exeption을 하나로 처리할 수 있어서 좋다 생각한다.
정상 흐름을 정의하라
위에처럼 캡슐화를 해서 작업하면 비즈니스와 오류처리가 잘 분리된다 이야기한다. → 허나 분리될 때가 적합하지 않을때가 있다.
예시처럼 m_total += expenses.getTotal() 함수를 catch 시에도 작성하는 것보다 PerDiemMealExpenses implements MealExpenses 로 묶어서 거기다 처리하는 방식 -> 특수사례를 처리하는 패턴
null을 반환하지 마라
null을 반환할거면 Exception을 던지거나 List 같은 경우, Collections.emptyList()를 던져라
null을 전달하지 마라
메서드로 null을 전달하는 코드를 피해라, 이것같은 경우는 매번 들어오는 함수에 대한 처리도 비용낭비이며 저나느 null을 넘기지 않도록 금지하는 정책을 추천했다.
As a result
우리는 앞단의 내용들을 사용하면서 오류처리에 대한 건도 같이 생각해야한다. 깨끗한 코드만 바라보다 정작 오류만 빵빵터지는 프로그램이 될 수도 있기 때문이다.
8장 [경계]
오픈소스, 패키지, 컴포넌트 .. 우리는 모든 것을 직접 손으로 구현할 수 없기에 다른 코드들을 가져올 때가 많다. 이때 깔끔하게 통합하는 법을 알아야한다.
외부코드 사용하기
Map같은 경우 유연성이 좋고 Map 내의 데이터 관리 및 제공 기능이 많다. 허나 그만큼 외부에서도 사용할 수 있는 기능(함수 등등..) 많고, 상태변화에 대해 취약하다. ex) 객체유형
Map을 깔끔하게 사용하려면 객체안에 숨긴다. 이러면 외부에서 접근 할 때 사용할 수 있는 내용이 객체가 선언한 기능으로 제한된다. 허나 중요한 것은 Map을 여기저기에 넘기지 말라한다.(노출주의)
경계 살피고 익히기
타 라이브러리를 우리쪽 코드로 작성하면, 우리버그인지 라이브러리 버그인지 골치아프다.
학습 테스트 : 외부 코드를 우리 코드로 호출하게 하며, 테스트 케이스를 작성해 외부 코드를 익혀라
아직 존재하지 않는 코드를 사용하기
아는 코드와 모르는 코드를 분리하는 경계 : 다른 모듈에 대한 코드를 모를 때 자체적 인터페이스를 정의하여 구현하기
깨끗한 경계
경계를 나누다보면 변경이 될 일들이 생긴다. 소프트웨어 설계가 매우 훌륭하다면 이런 일에 비용이 크게 발생하지 않는다. 따라서 경계에 위치하는 코드는 깔끔하게 분리하면 좋다. Adapter 패턴 이나 외부 라이브러리를 감싸서 우리의 기능으로 사용하자.
9장 [단위 테스트]
나는 팀원 분들과 작은 이야기 속에서, 테스트 코드 작성을 좋아하지 않는다고 말했다.
왜 그렇게 대답했나면, 지금 현재 작성중인 코드들은 단순 CRUD이며, 현재 프로젝트 코드를 Converting하는 작업에 있고,
입사 후 나만 아직 뚜렷한 결과물을 가져오지 못했기에 스스로 불안함이 있기 때문이다.
테스트 코드를 중요하게 생각하는 것에 있어서는 동의한다. 허나 아직 핵심 비즈니스 로직 외에는 테스트 코드 작성을 여유있게 둘 정도로 생각하지 않기 때문이다.
그렇다고 팀원들과의 이야기에서 고지식하게 전 테스트 코드를 왜 작성합니까! 라고 말하는 것은 아니고,
불안감에 망설이는 중이다. (2023.11.19 기준)
TDD 법칙 3가지
실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위테스트 작성한다.
현재 실패하는 테스트를 통화할 정도로만 실제 코드를 작성한다.
이런식으로 작성하면 엄청나게 크고 많은 테스트들이 나오고, 우리는 관리하기가 어려워진다.
깨끗한 테스트 코드 유지하기
저자는 테스트 코드는 실제 코드 못지 않게 중요하다 강조한다!
깨끗하게 짜는 법
가독성 2. 가독성 3. 가독성 → 명료성, 단순성, 풍부한 표현력
BUILD-OPERATE-CHECK 패턴 : 테스트 자료 만들기 → 조작 → 결과 체크 (given-when-then)
도메인에 특화된 테스트 언어( 간결하고 표현력이 풍부한 코드로의 리팩토링)
이중 표준 : 실제 코드만큼 효율적인 필요는 없다.
저자가 추천하는 것은 각 상태에 대해 함수로 따로 빼서 작성을 진행하던지, StringBuffer를 통해 원하는 상태랑 assertEquals를 하라는 것이다.
테스트당 Assert 하나
테스트에 assert가 여러개 필요하다면, 테스트를 쪼개서 각 assert를 수행하라
given-when-then 관례를 사용하여 쪼개놓고 Template Method 패턴으로 Then 부분을 자식 클래스에 두면 된다.
허나 Assert 하나하나 나누면 나중에 많은 변수들을 체크할 때 복잡해질 수도 있다.
테스트당 개념 하나
F.I.R.S.T
F = Fast : 빠르게
I = Independent : 독립적
각 테스트는 서로 의존하면 안된다. 하나가 실패하면 나머지도 실패하므로 후반에 힘들어진다.
R = Repeatable: 반복가능하게
모든 환경에서 여러번 테스트해도 돌아가도록 → 안되면 환경 탓(남탓)을 한다.
S = Self-Validating: 자가검증
Boolean으로 결과를 내자.
T = Timely : 적시에
테스트하려는 실제 코드를 구현하기 전에 구현한다.
10장 [클래스]
클래스 체계
변수와 유틸리티 함수는 공개하지 않는 편이 낫지만 반드시 숨겨야 할 필요가 없다.
클래스는 작아야 한다.
클래스 이름은 클래스 책임을 기술해야한다.
단일 책임 원칙(SRP)
변경할 이유가 하나, 단 하나여야 한다.
우리는 잘 지키려고 시작하지만 막상 진행하다보면 여러 책임을 진 코드를 발견할 때가 있다.
프로그램이 돌아가면 일이 끝났다는 생각 → X,
단일 책임 클래스가 많아지면 큰 그림 이해하기 어려워진다 (이 문장은 공감한다)
응집도
인스턴스 변수가 작아야 한다. → 응집도가 높아질 수록 쪼개야 한다.
이러한 기준을 두고 리팩토링 시, 더 좋은 코드로 바뀔 수 있다.
변경하기 쉬운 클래스
유지보수를 하다보면 클래스를 고칠 때가 많았고, 그러다보면 클래스는 변경하기 쉬워야한다는 말에 쫑긋했다.
저자는 추상클래스로 만들어서 처리할 때를 좋다 말한다. (아직까지 추상클래스로는 나눠본 사례는 없기에.. 다른분들의 경험을 들어보고 싶다.)
변경으로부터 격리
예시 코드를 보면 결합도를 낮추는 것이 테스트코드 작성할 때 어떤지 확인 할 일이 생기길..
11장 [시스템]
시스템 제작과 시스템 사용을 분리하라
소프트웨어 시스템은 준비 과정과 런타임 로직을 분리해야한다.
초기화 지연, 계산 지연이란 기법으로 설명했는데, 이러한 설정 논리는 일반 실행 논리와 분리해야 모듈성이 높아진다 설명함.
체계적이고 탄탄한 시스템 : 손쉬운 기법으로 모듈 성을 깨지 않는, 설정 논리와 일반 실행 논리 분리 등등 있다.
Main 분리
시스템 생성과 사용을 분리 → 생성은 main에서 실행은 application에서 하도록 ( @SpringBootApplication 과 비슷한?
팩토리
객체 생성시점을 어플리케이션에서 결정할 필요가 생긴다. 추상 팩토리 패턴을 사용
Factory.create 같이 선언 시점을 어플리케이션에서 결정한다.
의존성 주입
제어 역전 : 한 객체가 맡은 보조 책임을 새로운 객체에게 전적으로 넘긴다.
의존성 관리 : main, 컨테이너 사용(Spring Ioc Container, DI Container)
대다수 컨테이너는 필요할 때까지 객체를 생성하지 않고, 지연이나 최적화에 쓸 수 있도록 호출하거나 프록시 생성하는 방법 제공
확장
처음부터 올바르게 시스템을 만들 수 있다는 믿음은 미신이다.
시스템 아키텍처는 손대는게 큰 비용이다.
관심사를 적절하게 분리해 관리한다면, 아키텍쳐는 발전할 수 있다. ex) Entity → Dto 변환
횡단 관심사
영속성 같은 관심사 = 영속성 프레임워크 모듈화 (spring-context.xml)
횡단 관심사를 대처하기 위해 AOP를 사용한다. Aspect 방식으로 접근
자바 프록시
메서드 호출을 감싸는 경우 인데 코드가 늘어나며 복잡해져 사용 X
순수 자바 AOP 프레임워크
스프링은 비즈니스 논리를 POJO로 구현 (도메인 초점)
예시에서는 app.xml을 통해 접근하는걸로 보여지는데, xmlBeanFactory.getBean("bean 이름");
이렇게하면 java랑은 연관이 있지만, spring이랑은 연관이 없는 독립적 코드이다.
사실 이러한 내용들에 있어서 그렇다는 건 알겠는데, 상세하게 기술이 써진다 이런거는 체감이 부족한 것 같다.
AspectJ 관점
언어 차원에서 관점을 모듈화 구성으로 지원 (어디 언어에서나 사용이 유용함)
애너테이션을 사용하여 쉽게 사용하도록 기능 제공
테스트 주도 시스템 아키텍처 구축
도메인 논리로 아키텍처 구축이 가능하다면, TDD 아키텍처가 가능해진다.
단순하면서 잘 분리된 아키텍처를 진행하여 결과물 출시 후 확장하는 방식도 괜찮다.
의사 결정을 최적화하라
제일 위에가 결정하지말고 가장 적합한 사람한테 책임을 맡겨라.
마지막까지 미루는 것이 최선의 선택일 때도 있다.
명백한 가치가 있을 때 표준을 현명하게 사용하라
우리가 흔히 알고 쓰는 기술과 규칙이 단순 그냥 아니깐 사용하는 건지, 필요함에 있어 사용하는 건지 생각해라
결론 : 추상화 단계에서 의도를 명확히 표현, POJO를 작성해 구현 관심사 분리, 단순한 수단 사용
결과적으로 전부 무슨 내용이고, 어떤 것이 좋다 말할 때, 완벽하게 파악이 잘 되지는 않는다. 허나 이러한 시스템 아키텍처의 중요성이 코드를 작성하고, 유지 확장해나가는데 중요한 것이라는 것은 느꼈고 더 이런 부분까지 생각해서 구현하는 사람이 되어야 한다.
12장 [창발성]
설계는 ‘단순하다’
모든 테스트를 성공적으로 실행
중복 코드 제거
프로그래머 의도를 명확히 표현,
클래스, 메서드 수를 최소로 줄이기
모든 테스트를 실행하라
시스템이 의도한대로 돌아가는 지 검증할 방법이 없다면, 문서 작성에 대한 노력과 비용 가치가 줄어든다.
테스트 케이스를 만들고 계속 돌리면 객체지향적으로 바꾼다.(품질 상승)
리팩터링
코드는 점진적으로 리팩터링
응집도 (상승) 결합도 (하락) 관심사 분리, 모듈화, 함수 클래스 크기 줄이고, 더 나은 이름 선택,
+ 중복 제거, 의도 표현, 클래스 메서드 최소로 줄이기
중복을 없애라
Template Method 패턴을 이용해 중복을 제거
표현하라
좋은 이름 선택
함수 클래스 크기를 가능한 줄인다.
표준 명칭
클래스와 메서드 수를 최소로 줄여라
경험에서 나온 창발성은 매우 중요하다 생각한다. 우리는 이론이 아닌 실무에 있는 사람들이니깐.
허나,(이번에도 허나를 왜치는 나..) 나는 실제로 코드에 대한 경험이 적은 사람들에게는 이 내용을 참고만 하고, 나중에 제대로 접했으면 좋겠다는 생각을 한다.
이론만 가득한 입장에서 신입이나, 회사의 코드를 접했을 때 분명 클린코드의 말에 맞지 않게 해놓은 코드가 대다수라고 생각할거다. (나도 여러 업체들의 소스들을 보았지만, 클린코드처럼 정말 깔끔하게 짜놓은 코드는 없었다.)
그리고 이들의 코드를 보며 나쁜 코드라고 접했고, 그렇게 자신이 리팩토링을 하다보면, 아키텍처까지 가기 전에 결국 고치지 못할 것이라고 생각할 것이다. 그리고 그 코드들은 그 팀원들끼리 규약을 있는 코드이고…
그렇게 팀원들의 코드 스타일, 규약, 템플릿 등을 보며 이사람은 클린 코드 책을 읽지 않았어! 코드를 못짜는 팀이야! 라는 생각을 가지게 될 확률이 높으며 그건 스스로를 좀먹는 일이 될거라 생각하기 때문이다.
그래서 난 이번장은 좋은 내용이자 동시에 위험한 내용(성급한 일반화의 오류)일 수 있다 생각하여 최소 2, 3개의 프로젝트의 시스템들을 겪으며 이 내용을 접목하면 좋겠다.
Touch background to close