[클린코드 스터디] 2장. 의미있는 이름 (요약정리와 적용)
내용 간단히 요약하기
의도를 분명히 밝혀라
• 좋은 이름을 지으려면 시간이 걸리지만, 덕분에 더 많은 시간 절약할 수 있다.
• 코드가 하는 일을 짐작하게 하는 이름을 사용하자.
// 나쁜 예
let a = 10;
// 좋은 예
let maxUserCount = 10;
그릇된 정보를 피하라
• 코드에 잘못된 단서를 남기지 말자.
• 각 개념의 차이가 명백히 드러나도록 이름을 짓자.
// 나쁜 예: 숫자가 밀리초인지 초인지 불분명
let timeout = 5000;
// 좋은 예
let timeoutInMilliseconds = 5000;
의미있게 구분하라
• 컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어(noise word)를 추가하는 방식은 적절하지 못하다. 이름이 달라야 한다면 의미도 달라져야 한다.
• 읽는 사람이 차이를 알도록 이름을 지어라.
// 나쁜 예
let user1;
let user2;
// 좋은 예
let activeUser;
let inactiveUser;
발음하기 쉬운 이름을 사용하라
• 발음하기 어려운 이름은 토론하기도 어렵다.
// 나쁜 예
let usrNm;
// 좋은 예
let userName;
검색하기 쉬운 이름을 사용하라
• 이름 길이는 범위 크기에 비례해야 한다.
• 변수와 상수를 코드 여러 곳에서 사용한다면 검색하기 쉬운 이름이 바람직하다.
// 나쁜 예
const a = 'https://api.example.com/users';
// 좋은 예
const USERS_API_ENDPOINT = 'https://api.example.com/users';
인코딩을 피하라: 헝가리안 표기법
• 옛날에는 컴파일러가 타입을 점검하지 않았으므로 프로그래머가 타입을 기억할 단서가 필요했다. 하지만 요즘은 컴파일러가 타입을 기억하고 강제한다.
• IDE는 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 발전했다. 따라서 헝가리안 표기법이나 기타 인코딩 방식이 오히려 방해가 될 뿐이다.
// 나쁜 예: 헝가리안 표기법 사용
let strName = 'John Doe';
// 좋은 예
let name = 'John Doe';
멤버 변수 접두어
• 이제는 멤버 변수에 m_이라는 접두어를 붙일 필요도 없다. 클래스과 함수는 접두어가 필요없을 정도로 작아야 마땅하다.
// 나쁜 예
class User {
constructor(name) {
this.m_name = name;
}
}
// 좋은 예
class User {
constructor(name) {
this.name = name;
}
}
인터페이스 클래스와 구현 클래스
• 때로는 인코딩이 필요한 경우도 있다.
• 인터페이스 이름은 접두어를 붙이지 않는 편이 좋다. 그래서 인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면 구현 클래스 이름을 택하겠다.
// 나쁜 예
class IUser {
// ...
}
class User implements IUser {
// ...
}
// 좋은 예
class User {
// ...
}
class UserService {
// ...
}
자신의 기억력을 자랑하지 마라
• 독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다.
• 문자 하나만 사용하는 변수 이름은 문제가 있다.
• 루프에서 반복 횟수를 세는 변수 i, j, k는 괜찮다. (소문자 l은 절대 안 된다.) 단, 루프 범위가 아주 작고 다른 이름과 충돌하지 않을 때만 괜찮다.
// 나쁜 예
class IUser {
// ...
}
class User implements IUser {
// ...
}
// 좋은 예
class User {
// ...
}
class UserService {
// ...
}
클래스 이름
• 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
• 예: Customer, WikiPage, Account, AddressParser 등
• Manager, Processor, Data, Info 등과 같은 단어는 피한다.
• 동사는 사용하지 않는다.
// 나쁜 예
class DataManager {
// ...
}
// 좋은 예
class UserRepository {
// ...
}
메서드 이름
• 메서드 이름은 동사구가 적합하다.
• 예: postPayment, deletePage, save 등
• 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 Javabean 표준에 따라 값 앞에 get, set, is를 붙인다.
• 생성자(Constructor)를 중복정의(Overload)할 때는 정적 팩토리 메서드를 사용한다.
• 메서드는 인수를 설명하는 이름을 사용한다.
// 나쁜 예
class User {
constructor(name) {
this.name = name;
}
doSomething() {
// ...
}
}
// 좋은 예
class User {
constructor(name) {
this.name = name;
}
updateName(newName) {
this.name = newName;
}
isActive() {
return this.active;
}
}
기발한 이름은 피하라
• 재미난 이름보다 명료한 이름을 선택하라.
• 의도를 분명하고 솔직하게 표현하라.
한 개념에 한 단어를 사용하라
• 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
• 동일한 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.
• 일관성 있는 어휘는 코드를 사용할 프로그래머에게 유익하다.
// 나쁜 예
class UserService {
getUser(id) { /* ... */ }
}
class ProductService {
fetchProduct(id) { /* ... */ }
}
// 좋은 예
class UserService {
getUser(id) { /* ... */ }
}
class ProductService {
getProduct(id) { /* ... */ }
}
말장난을 하지 마라
• 한 단어를 두 가지 목적으로 사용하지 않는다.
• 다른 개념에 같은 단어를 사용하는 것은 말장난에 불과하다.
• 프로그래머는 코드를 최대한 이해하기 쉽게 짜야 한다.
• 탐구가 필요한 코드보다는 대충 훑어봐도 이해할 수 있는 코드를 작성하라.
// 나쁜 예
let handle = 'fileHandler';
// 좋은 예
let fileHandler = 'fileHandler';
해법 영역에서 가져온 이름을 사용하라
• 코드를 읽을 사람도 프로그래머임을 명심하라.
• 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
• 모든 이름을 문제 영역에서 가져오는 것은 현명하지 못하다.
• 기술 개념에는 기술 이름이 적합하다.
// 좋은 예
class Observable {
// ...
}
class debounce {
// ...
}
문제 영역에서 가져온 이름을 사용하라
• 적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져온다.
• 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 사용하라.
// 문제 영역: 전자상거래
class ShoppingCart {
addItem(item) { /* ... */ }
removeItem(itemId) { /* ... */ }
}
의미 있는 맥락을 추가하라
• 스스로 의미가 분명한 이름이 없지 않다.
• 대다수 이름은 그렇지 않기 때문에, 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.
• 모든 방법이 실패하면 접두어를 사용한다.
• 예: firstName, lastName, street, houseNumber, city, state, zipcode는 주소임을 알 수 있다. 하지만 state만 사용하면 주소 일부임을 모를 수 있다. addr 접두어를 쓰면 맥락이 분명해진다.
// 나쁜 예
let state;
// 좋은 예
let addressState;
불필요한 맥락을 없애라
• 의미가 분명할 경우 짧은 이름이 긴 이름보다 좋다.
• 이름이 불필요한 맥락을 추가하지 않도록 주의한다.
• accountAddress와 customerAddress는 Address 클래스의 인스턴스로 적합한 이름이지만, 클래스 이름으로는 Address가 적합하다.
// 나쁜 예
class AccountAddress {
// ...
}
class CustomerAddress {
// ...
}
// 좋은 예
class Address {
// ...
}
배운 내용을 적용해보자 - 헬퍼함수 정리
오늘자 클린코드를 읽다보니, 회사 코드에서 지금 어마무시하게 흩어져있는 헬퍼 함수들이 생각났다…🥲
정리해야한다는 생각만 하고있고 정리를 못하고있었는데, 이 기회에 배운 내용을 적용해서 추상화를 통해 클래스와 매서드로 정리해보고자 한다.
1. 이전에 지저분하게 관리되던 헬퍼 함수들의 상태
이전에는 캠프와 관련된 다양한 헬퍼 함수들이 하나의 파일에 무질서하게 모여 있었다. 이러한 함수들은 특정 기능별로 명확하게 구분되지 않았고, 프로젝트가 커짐에 따라 관리와 유지보수가 점점 어려워졌다. 또한, TypeScript로 작성되어 있었지만 타입 안정성이 충분하지 않았고, 함수 간의 의존성이나 상호작용이 명확하지 않아 코드의 가독성이 떨어졌다.
// CampHelpers.ts
export const formatShortenDate = (dateStr: string) => { /* ... */ };
export const getRegStatus = (camp: CampListElement | CampDetailElement) => { /* ... */ };
export const getTuition = (camp: CampListElement | CampDetailElement) => { /* ... */ };
export const getAdLabelText = (camp: CampListElement) => { /* ... */ };
// 수십 개의 헬퍼 함수들이 한 파일에 혼재
2. 문제점 및 불편함
- 조직화 부족: 헬퍼 함수들이 특정 기능별로 분류되지 않아 필요한 함수를 찾기 어렵고, 관련 함수 간의 연관성을 파악하기 힘들었다.
- 유지보수의 어려움: 함수들이 한 파일에 모여 있어 코드 변경 시 의도치 않은 부작용이 발생할 위험이 높았다.
- 재사용성 저하: 함수들이 특정 상황에 종속적이어서 다른 모듈이나 컴포넌트에서 재사용하기 어려웠다.
- 가독성 저하: 함수 이름이 일관되지 않고, 주석이 부족하여 코드의 목적을 이해하기 어려웠다.
3. 개선 방안 및 선택 이유
개선 방안:
- 클래스 기반 구조 도입: 관련된 헬퍼 함수들을 기능별로 클래스에 묶어 모듈화함으로써 코드의 조직화를 강화했다.
- 모듈화 및 파일 분리: 각 유틸리티 클래스를 별도의 파일로 분리하여 관리함으로써 유지보수성과 확장성을 향상시켰다.
- 명확한 네이밍과 주석 추가: 함수와 클래스의 이름을 명확하게 정의하고, 주석을 추가하여 코드의 목적과 사용법을 쉽게 이해할 수 있도록 했다.
- 클린 코드 원칙 적용: 함수 및 메서드의 이름을 의미 있게 변경하고, 일관된 네이밍 규칙을 적용하여 코드의 가독성과 유지보수성을 높였다.
4. 결과적으로 어떻게 개선되었는지
- 코드의 가독성 및 유지보수성 향상: 관련된 함수들이 기능별로 클래스로 묶여 있어 코드의 흐름을 쉽게 파악할 수 있게 되었다.
- 타입 안정성 확보: TypeScript를 더욱 효과적으로 활용하여 코드의 타입 안정성을 높였고, 개발 시점에 오류를 사전에 발견할 수 있게 되었다.
- 모듈화 및 재사용성 증대: 각 유틸리티 클래스가 독립적으로 동작하며, 필요한 곳에서 쉽게 임포트하여 사용할 수 있게 되었다.
- 확장성과 유연성 향상: 새로운 기능 추가 시 기존 클래스를 확장하거나 새로운 클래스를 추가하는 방식으로 손쉽게 대응할 수 있게 되었다.
- 코드의 일관성 유지: 명확한 네이밍 규칙과 주석을 통해 코드의 일관성을 유지하며, 협업 시 다른 개발자들도 쉽게 이해하고 사용할 수 있게 되었다.
- 클린 코드 준수: 함수 및 메서드의 이름을 의미 있게 변경하고, 불필요한 복잡성을 줄여 코드의 가독성과 유지보수성을 더욱 향상시켰다.
5. 개선된 파일 구조
개선된 유틸리티 클래스들을 효과적으로 관리하기 위해 다음과 같은 파일 구조를 채택했다:
src/
├── utils/
│ ├── CampAdUtils.ts
│ ├── CampCategoryUtils.ts
│ ├── CampCostUtils.ts
│ ├── CampDateUtils.ts
│ ├── CampFilterUtils.ts
│ ├── CampMiscUtils.ts
│ ├── CampQualificationUtils.ts
│ ├── CampStatusUtils.ts
│ └── CampUtils.ts
├── static/
│ └── constants.ts
└── ... (기타 디렉토리 및 파일)
6. 클린 코드 네이밍 적용 및 함수 이름 개선
클린 코드 원칙을 적용하여 각 유틸리티 클래스의 함수 이름을 의미 있고 일관되게 변경했다. 주요 변경 사항은 다음과 같다:
- 의미 있는 이름 사용: 함수의 역할을 명확하게 나타내는 이름으로 변경했다.
- 일관된 네이밍 규칙: 동사+명사 형태를 사용하여 함수의 동작을 명확히 했다.
- 짧고 간결한 이름: 불필요하게 긴 이름을 피하고, 간결하게 표현했다.
- 단일 책임 원칙 준수: 각 함수가 하나의 책임만 가지도록 이름을 정했다.
이전 이름 변경된 이름 적용된 클린 코드 원칙
formatShortenDate | formatDateShort | 의미 있는 이름, 동사+명사, 간결함 |
getRegStatus | determineRegistrationStatus | 명확한 역할 표현, 동사 사용 |
getTuition | calculateTuitionFee | 구체적인 역할 표현, 동사 사용 |
getAdLabelText | generateAdLabel | 명확한 역할 표현, 동사 사용 |
handleOutsideClick | processOutsideClick | 의미 있는 이름, 동사 사용 |
ConvertNumbers | convertStringToNumber | 명확한 역할 표현, 동사+명사 |
hexToRgb | convertHexToRgb | 명확한 역할 표현, 동사+명사 |
7. 개선된 코드의 사용 예시
개선된 유틸리티 클래스들을 사용하는 방법을 간단한 예시와 함께 소개한다.
예시 1: 날짜 포맷팅
// 다른 파일에서 CampUtils 사용 예시
import { CampUtils } from './utils/CampUtils';
// 날짜 포맷팅
const formattedDate = CampUtils.Date.formatDateShort('2024-10-15');
console.log(formattedDate); // "10/15"
예시 2: 등록 상태 가져오기
import { CampUtils } from './utils/CampUtils';
import { CampListElement } from "@/types/camp";
const camp: CampListElement = { /* 캠프 데이터 */ };
// 등록 상태 가져오기
const registrationStatus: string = CampUtils.Status.determineRegistrationStatus(camp);
console.log(registrationStatus); // 예: "등록 종료"
예시 3: 비용 정보 가져오기
import { CampUtils } from './utils/CampUtils';
import { CampDetailElement } from "@/types/camp";
const camp: CampDetailElement = { /* 캠프 데이터 */ };
// 비용 정보 가져오기
const tuitionFee: string = CampUtils.Cost.calculateTuitionFee(camp);
console.log(tuitionFee); // 예: "유료 (100,000원)"
예시 4: 광고 라벨 텍스트 가져오기
import { CampUtils } from './utils/CampUtils';
import { CampListElement } from "@/types/camp";
const camp: CampListElement = { /* 캠프 데이터 */ };
// 광고 라벨 텍스트 가져오기
const adLabel: string | undefined = CampUtils.Ad.generateAdLabel(camp);
console.log(adLabel); // 예: "🎁 EVENT"
이번 개선 과정을 통해 캠프 관련 헬퍼 함수들을 기능별로 클래스화하고, TypeScript를 더욱 효과적으로 활용하여 코드의 가독성, 유지보수성, 타입 안정성을 크게 향상시켰다. 클린 코드 원칙을 적용하여 함수 및 메서드의 이름을 의미 있고 일관되게 변경함으로써 코드의 이해도와 재사용성을 높일 수 있었다.