저희 회사에는 각 층에 작은 도서관이 있습니다. 개발 서적은 물론이고 비개발 서적도 많아 사옥 이름이 그렇게 지어진 게 아닌가 싶을 정도입니다. 지난주에 오랜만에 출근을 해 이번 연휴(토~월) 동안 읽을만한 책을 하나 빌려왔는데, 선정 기준은 아래와 같았습니다.
- 3일 동안 읽을 수 있는 분량
- 지금 진행중인 프로젝트에 도움이 될만한 내용
젠킨스 책을 빌릴까 고민했다가 이게 좀 더 읽기 쉬울 거 같기도 하고, 동료의 추천도 있어 이 책을 빌려오게 되었습니다!
이 책은 0장에서 리팩토링에 대한 이해, 그 이후 장에서 예제 코드와 함께 리팩토링을 진행합니다. 이번 포스팅에서는 0장에서 소개하는 리팩토링에 대해 포스팅하도록 하겠습니다.
리팩토링
리팩토링은 외부에서 보는 프로그램 동작은 바꾸지 않고 프로그램 내부 구조를 개선하는 것을 의미합니다.
외부는 프로그램을 사용하는 사용자일 수도 있고, 클래스나 메서드를 사용하는 다른 클래스나 메서드일 수 있습니다.
중요한 점은 외부에서 보는 프로그램의 동작이 바뀌지 않는다는 점입니다.
- 리팩토링이 맞는 경우 : 소스 코드 정리
- 리팩토링이 아닌 경우 : 버그 수정, 기능 추가, 소스 코드 정리
리팩토링의 목적과 한계
리팩토링은 아래 목적을 가지고 있습니다.
- 버그를 발견하기 쉽게 만든다
- 기능을 추가하기 쉽게 만든다
- 리뷰하기 쉽게 만든다
리팩토링을 함으로써 코드가 정리되고 개선되어 리뷰하기 쉽게 만들어지고, 이 과정 중에서 버그가 발견되거나 이후에 기능을 추가하기 쉬워집니다. 때문에 기능 구현이 완료된 이후에는 반드시 리팩토링을 해주는 것이 좋습니다.
반면에, 리팩토링에는 불가능한 시점(한계)이 있는데 아래와 같습니다.
- 프로그램이 아직 동작하지 않을 때
만들어지지 않은 기능은 리팩토링할 수 없습니다. 완성되지 않았기 때문에 프로그램의 동작이 얼마든 변경될 수 있기 때문입니다. - 시간이 너무 촉박할 때
무엇보다 중요한 것은 완성입니다. 리팩토링 때문에 다른 기능을 개발할 시간이 모자라게 된다면, 그것은 안 하느니만 못합니다.
코드의 악취 맡기
리팩토링은 코드의 악취를 맡는 것에서 시작합니다.
[리팩토링: 코드 품질을 개선하는 객체지향 사고법] 에서는 프로그램에서 리팩토링이 필요한 부분을 악취가 난다고 표현합니다. 느낌이 딱 오시죠? 사람마다 다르게 표현하긴 할 텐데 저 같은 경우에는 옛날에 처음 자바 개발을 배울 때 이것을 감성 코딩이라고 불렀었습니다. 그리고 제 블로그의 이름이 논리적 코딩인 이유도 감성 코딩을 하지 말자!라는 의미를 가지고 있습니다(TMI)
다시 돌아와서, 소스 코드를 보다 아래와 같은 생각이 든다면 그 코드는 아마 악취가 나는 코드일 겁니다.
- 겹치잖아!
- 너무 길어!
- 너무 많아!
- 이름이 안 맞잖아!
- 너무 공개적이잖아!
- 객체 지향답지 않아
그리고 [리팩토링: 코드 품질을 개선하는 객체지향 사고법] 에서는 코드의 악취를 좀 더 상세하게 명세해주고 있는데요, 아래와 같습니다.
강조 표시된 목록은 제가 이번 프로젝트에서 만들어낸 악취입니다.
악취명 | 내용 | 제 문제점 |
중복 코드 | 같은 코드가 곳곳에 중복되어 있다. | |
너무 긴 메서드 | 메서드가 너무 길다. | 메서드가 너무 길어 주석으로 각 단계별로 어떤 동작을 수행하는지 적었습니다. // 0. 블라블라 ... // 1. 블라블라 ... |
방대한 클래스 | 클래스의 필드나 메서드가 너무 많다. | |
과다한 매개변수 | 메서드가 받는 매개변수 개수가 너무 많다. | |
변경 발산 | 사양 변경이 있을 때 수정 내용이 곳곳에 흩어져 있다. | |
변경 분산 | 어떤 클래스를 수정하면 다른 클래스도 수정해야 한다. | |
속성, 조작 끼어들기 | 언제나 다른 클래스 내용을 수정하는 클래스가 있다. | |
데이터 뭉치 | 합쳐서 다뤄야 할 데이터가 한 클래스에 모여 있지 않다. | |
기본 타입 집착 | 클래스를 만들 지 않고 int 같은 기본 타입만 사용한다. | |
스위치 문 | switch 문이나 if 문으로 동작을 나눈다. | switch문으로 필요한 클래스를 인스턴스화 하도록 했었습니다. Interface a = null; switch (condition) { case 1: a = new ClassA(); case 2: b = new ClassB(); } |
평행 상속 | 하위 클래스를 만들면 클래스 계층의 다른 곳에도 허위 클래스를 만들어야 한다. | |
게으른 클래스 | 클래스가 별로 하는 게 없다. | 필요하다고 판단해 만든 클래스가 달리 하는게 없었습니다. |
의심스러운 일반화 | '언젠가 이런 확장을 하겠지'라고 너무 일반화한다. | 지나치게 확장을 고려해 막상 현재 요구사항에서는 불필요한 부분이 생겼었습니다. |
임시 속성 | 임시로만 쓰는 필드가 있다. | |
메시지 연쇄 | 메서드 호출 연쇄가 너무 많다. | |
중개자 | 맡기기만 하고 자신은 일하지 않는 클래스가 있다. | |
부적절한 관계 | 그럴 필요가 없는데도 양방향 링크를 걸거나 IS-A 관계가 없는데 상속을 사용한다. | |
클래스 인터페이스 불일치 | 클래스 인터페이스(API)가 적절하지 않다. | |
불완전한 라이브러리 클래스 | 기존 라이브러리 클래스를 사용하기 어렵다. | |
데이터 클래스 | 필드와 getter 메서드와 setter 메서드뿐인 클래스가 있다. | |
상속 거부 | 상속한 메서드인데 호출하면 문제가 발생한다. | |
주석 | 코드의 모자란 점을 설명하기 위해 자세한 주석이 붙어있다. | 주석을 상세하게 다는게 맞다고 생각해 길고 자세하게 달았습니다. 그런데 나중에 보니 코드에 대한 변명이 써있었습니다. |
리팩토링 에센스
리팩토링을 할 때 이것을 반드시 명심해야 합니다.
스텝 바이 스텝
두 가지 수정을 한꺼번에 하지 않기
리팩토링은 작아도 한 걸음 씩 확실히 반복해서 개선하는 기술입니다. 한 번에 수정에서는 한 가지만 수정합니다. 한꺼번에 여러 수정을 할 경우 그만큼 리팩토링에 실패할 위험이 높아지기 때문입니다.
되돌리기 쉽게 하기
리팩토링 이후에 어떤 문제가 생겨 원래대로 되돌려야 할 수 있습니다. 이때 리팩토링을 스텝 바이 스텝으로 진행했다면 되돌리기 훨씬 수월할 것입니다.
단계마다 확인
누구나 실수할 수 있습니다. 때문에 각 단계마다 프로그램을 컴파일하고, 테스트해 리팩토링이 정상적으로 진행되었는지 확인합니다.
오래된걸 새로운 걸로 바꿈
리팩토링은 '모든 걸 부수고 모두 다시 만든다'가 아니라 '동작하는 상태를 유지하면서 새로운 코드를 추가해서 오래된 것이 모두 새로워지면 오래된 것을 제거한다'라는 절차를 택합니다. 귀찮더라도 실패할 위험이 적기 때문입니다.
'☕️ JAVA' 카테고리의 다른 글
SXSSFWorkbook 생성 시 FontConfiguration에서 NullPointerException이 발생할 때 (0) | 2021.11.25 |
---|---|
Try with Resources - 손쉬운 자원 해제 (0) | 2021.03.09 |
BufferedReader 가 Scanner 보다 빠른 이유 (0) | 2020.11.08 |
Enum을 사용해보자 (0) | 2020.10.18 |
WebSocketSession에서 HttpSession를 얻는 방법 (0) | 2020.09.10 |