✅ 클린 코드란?
클린 코드에 대해 유일하고 엄격한 정의는 존재하지 않는다. 하나의 통용되는 말이 있다면 바로 가독성이 높은 코드가 될 것이다. 즉, 클린 코드는 다른 개발자가 코드를 읽고 유지 관리를 할 수 있는지 여부에 달려 있다고 볼 수 있다. 클린코드로 코드를 작성하려면 다음과 같이 구현해야 한다.
- 네이밍이 잘 되어야 함(무슨 역할인지 명시)
- 변수, 함수, 클래스, 파일 이름 모두 해당
- 이름은 항상 "무엇을 하는지" 명확하게 드러내야 한다. 이름은 역할과 본질을 나타내 준다.
- 코딩할 때 가장 어려운 파트
- 오류가 없어야 함
- 중복이 없어야 함
- 중복 코드가 있다면 수정할 때 2번 이상 수정해야 함.
- 3번 이상 중복 코드가 등장하면, 어떻게든 한 곳으로 모아줘야 한다.
- 의존성을 최대한 줄여야 함
- 클래스 혹은 메소드가 한가지 일만 처리해야 함
✅ 클린코드가 왜 필요한가?
사실 내가 설계한 로직을 잘 기억하고 작성한 코드라면 내가 다시 읽는데에 큰 어려움이 없을지도 모른다. 하지만 우리는 협업이 많은 개발자이고, 이직이 잦으며, 새로운 기능들이 수정되고 추가되는 경우가 많기 때문에 코드를 다시 읽어야 하는 경우가 많다. 클린코드를 잘 지킨다면, 코트를 읽기 쉽게 만들어 새 코드를 작성하기도 쉬워진다.
🔎 클린코드의 주요 원칙
1. Follow Standard Convention (Coding표준, 아키텍처 표준 및 설계 가이드를 준수하라)
2. Keep it simple, Stupid (단순한 것이 효율적이다. 복잡함을 최소화하라)
3. Boy Scout Rule (참조되거나 수정되는 코드는 원래보다 clean해야 한다)
4. Root Cause Analysis (항상 근본적인 원인을 찾아라. 그렇지 않으면 반복될 것이다)
5. Do not multiple language in one source file (하나의 파일은 하나의 언어로 작성하라)
🔎 디자인 원칙(SOLID)
디자인 원칙(SOLID)은 이전에 정리한 글이 있으므로 아래에서 확인하면 될 것 같다.
✅ 클린 코드를 위한 고려사항
그 외 클린 코드를 위한 고려사항들은 다음과 같다.
1. 명확하고 간결한 주석
- 함수의 주요 세부사항은 주석으로 남기기(함수 선언으로는 알 수 없는 내용들)
- 보기 좋게 배치하고 정리하기
2. 읽기 쉽게 흐름제어 만들기
- 삼항 연산자는 꼭 필요하거나 간단한 경우에만 이용하기
- do-while문은 피하기
- 부정이 아닌 긍정문 사용하기
if a != 0: # 부정문 보다는
if a == 0: # 긍정문 사용하기
3. 함수는 가급적 작게, 하나의 작업만 수행하도록
✅ 코드 리팩토링이란?
현재 작동하는 코드들이 정상적으로 작동하는 것을 유지하되, 내부적으로 코드를 개선해 이해하고 수정하기 쉽게 만드는 작업을 리팩토링이라고 한다.
✅ 리팩토링은 왜 할까?
프로젝트 초기에 아무리 세심하고 치밀하게 설계를 해도 코딩을 시작하면 그때부터 디자인은 점차 망가지기 마련이다. 리팩토링을 한다는 것은 설계를 지속적으로 개선한다는 뜻이기 때문에, 소프트웨어 아키텍처가 좋아지고, 소프트웨어를 이해하기 쉬워지며 버그 또한 쉽게 찾을 수 있다. 이와 같은 이유로, 리팩토링을 하면 프로그래밍 속도가 빨라지게 된다.
🔎 리팩토링 예제 살펴보기
몇가지 리팩토링 예제를 살펴보면 다음과 같다.
1. 매개변수 객체 만들기
- 몰려다니는 데이터 무리를 데이터 구조 하나로 모아주자
- 데이터 구조로 묶으면 데이터 사이의 관계가 명확해진다.
# before
def amount_invoiced(start_date, end_date):
pass
def amount_received(start_date, end_date):
pass
# after
def amount_invoiced(date_range):
pass
def amount_received(date_range):
pass
변수를 하나의 객체로 묶음으로써 하나의 개념을 만들어내고, 이는 더 나은 디자인을 만들어 낼 수 있다.
2. 여러 함수를 클래스로 묶기
- 클래스로 묶으면, 함수들이 공유하는 공통 환경과 목적을 명확히 표현할 수 있다.
- 또한 함수 매개변수를 줄여서, 호출을 더 간결하게 만들 수 있다.
- 원하는 함수를 클래스 단위로 빠르게 찾을 수 있다.
# before
def base(reading):
pass
def charge(reading):
pass
def calculate_base_charge(reading):
pass
# after
class Reading:
def __init__(self, reading):
self.reading = reading
def base(self):
pass
def charge(self):
pass
def calculate_base_charge(self):
pass
3. 레코드 캡슐화하기
- 곳곳에 쓰이는 가변 데이터는 레코드가 아니라 객체로 저장하자.
- 데이터 구조를 명확히 표현할 수 있고, 코드 한 곳에서 관리하고 표현할 수 있게 된다.
# before
organization = {"name": "하키", "country": "North Korea"}
# after
class Organization:
def __init__(self, name: str, country: str):
self.name = name
self.country = country
4. 임시 변수를 질의함수로 바꾸기
- 곳곳에 쓰이는 임시변수를 method로 만들어, 굳이 임시변수를 더 만들지 말자
# before
base_price = self._quantity * self._item_price
if base_price > 1000:
return base_price * 0.95
else:
return base_price * 0.98
# after
def _get_base_price(self):
return self._quantity * self._item_price
if self._get_base_price() > 1000:
return self._get_base_price() * 0.95
else:
return self._get_base_price() * 0.98
5. 클래스 추출하기
- 개발 과정에서 점점 비대해지는 클래스를 적절히 분리한다.
- 단일 책임 원칙(SRP)를 잊지 말자.
# before
class Person:
def __init__(self, ..., office_area_code, office_number):
...
self.office_area_code = office_area_code
self.office_number = office_number
# after
class Person:
def __init__(self, ..., office_area_code, office_number):
...
self.TelephoneNumber(office_area_code, office_number)
class TelephoneNumber:
def __init__(self, office_area_code, office_number):
self.office_area_code = office_area_code
self.office_number = office_number
- 함께 변경되는 일이 많거나, 의존하는 데이터들도 분리한다.
- 반대로, 리팩토링을 거치면서 쓸모 없어진 클래스는 이 과정을 반대로 한다. 합친 뒤에 다시 살펴보면 새로운 클래스를 추출할 수도 있기 때문.
6. 조건문 분해하기
- 긴 조건문은 의도를 드러낼 수 있는 함수로 추출하여 로직을 명확히 하기
# before
if data.is_before(plan.summer_start) and not date.is_after(plan.summer_end):
charge = quantity * plan.summer_rate
else:
charge = quantity * plan.regular_rate + plan.regular_service_charge
# after
if is_summer():
charge = summerCharge()
else:
charge = regularCharge()
7. 조건문 통합하기
- 하나로 합칠 수 있는 조건식은 합친 뒤, 의도를 드러낼 수 있는 함수로 추출하자.
# before
if employee.seniority < 2: return 0
if employee.month_disabled > 12: return 0
if employee.is_part_tiem: return 0
# after
def is_not_eligible_for_disability():
return (employee.seniority < 2 or
employee.month_disabled > 12 or
employee.is_part_time)
if is_not_eligible_for_disability(): return 0
9. 특이 케이스 추가하기
# before
if customer == "미확인 고객":
customer_name = "거주자"
# after
class UnknownCustomer:
def __init__(self):
self.name = "거주자"
- 일반적으로, 특정 값에 대해 똑같이 반응하는 코드가 여러 곳이면, 그 반응을 한 데로 모으는게 효율적이다.
- 모으는 것은 리터럴 객체나 따로 정의한 클래스에 모을 수 있는데, 데이터를 담기만 하는 경우 리터럴 객체(dict와 같은)를 쓰면 되고, 어떤 동작을 수행해야 하면 클래스로 추출하면 된다.
- 널 객체 패턴이라고도 한다.
10. 어서션(assert) 추가하기
- 어서션은 어떤 상태임을 가정한 채 실행되는지 다른 개발자에게 알려주는 소통 도구이다.
# before
if self.discount_rate:
base -= self.discount_rate * base
# after
assert self.discount_rate >= 0
if self.discount_rate:
base -= self.discount_rate * base
- 어서션이 있고 없고가 프로그램의 정상 동작에 아무런 영향을 주지 않도록 작성되어야 한다.
- 즉, 어서션은 실패해서는 안된다. 실패한다면 어딘가 잘못 구현한 코드가 있는 것이다.
- 어서션은 개발자 간의 커뮤니케이션 도구임을 잊지 말자.
✅ 클린코드와 리팩토링의 차이
클린 코드는 단순히 가독성을 높이기 위한 작업으로 이루어져 있다면, 리팩토링은 클린 코드를 포함한 유지보수를 위한 코드 개선, 아키텍처 설계 등이 이루어진다.
클린코드와 같은 부분은 설계부터 잘 이루어져 있는 것이 중요하고, 리팩토링은 결과물이 나온 이후 수정이나 추가 작업이 진행될 때 개선해나가는 것이 올바른 방향이다.
📚 Reference
'Computer Science' 카테고리의 다른 글
TDD(Test Driven Development)란? (0) | 2023.01.31 |
---|---|
[DB] 데이터베이스 인덱스(Index)란 무엇인가? (0) | 2023.01.30 |
NoSQL이랑 RDBMS의 특징과 차이점 (0) | 2023.01.21 |
[앱의 종류] 네이티브 앱 & 웹 앱 & 하이브리드 앱 (1) | 2023.01.19 |
프로세스(Process)와 스레드(Thread) (0) | 2023.01.18 |