2장 의미 있는 이름
1. 📌 핵심 개념 정리
✅ 요약하기
-
의도를 분명히 밝혀라
좋은 이름을 지으려면 시간이 걸리지만, 좋은 이름으로 절약하는 시간이 훨씬 더 많다. 주석이 필요하다면, 이름이 의도를 분명히 드러내지 못했다는 신호일 수 있다. 코드의 맥락이 이름 자체에 명시적으로 드러나도록 작성해야 한다.-
개선 전 코드 예시:
getThem()
함수는 무엇을 하는지,theList
는 무엇을 담고 있는지,x[0] == 4
조건은 무엇을 의미하는지 코드만으로는 파악하기 어렵다.public List<int[]> getThem() { List<int[]> list1 = new ArrayList<int[]>(); for (int[] x: theList) if (x[0] == 4) list1.add(x); return list1; }
-
개선 후 코드 예시:
getFlaggedCells()
함수명,gameBoard
,cell
,STATUS_VALUE
,FLAGGED
,isFlagged()
등의 명확한 이름 사용으로 코드의 의도가 분명하게 드러난다.public List<int[]> getFlaggedCells() { List<int[]> flaggedCells = new ArrayList<int[]>(); for (int[] cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; }
-
변수명 개선 예시:
의미 없는d
대신,elapsedTimeInDays
,daysSinceCreation
등 측정 값과 단위를 포함한 이름을 사용한다.- 변경 전:
int d; // 경과 시간 (단위: 날짜)
- 변경 후:
int elapsedTimeInDays;
- 변경 전:
-
클래스와 함수를 활용한 개선:
단순 배열 대신Cell
클래스를 사용하고, 조건문을isFlagged()
함수로 추상화하여 코드의 명확성을 높일 수 있다.public List<Cell> getFlaggedCells () { List<Cell> flaggedCells = new ArrayList<Cell>(); for (Cell cell: gameBoard) if (cell.isFlagged()) flaggedCells.add(cell); return flaggedCells; }
-
- 그릇된 정보를 피해라
그릇된 정보는 코드의 의미를 흐리고 오해를 유발한다. 널리 쓰이는 약어나 특정 플랫폼 관련 용어, 유사한 이름의 변수/함수, 배열인데도 이름에 List를 포함하는 등의 경우 오해의 소지가 있다.- 혼란을 야기하는 이름 예시:
getActiveAccount()
,getActiveAccouts()
,getActiveAccountInfo()
등은 어떤 함수를 호출해야 할지 혼란을 줄 수 있다. - 개선 전:
int hp, aix, sco; int[] accountList; void XYZControllerHandlingOfStrings(); void XYZControllerStorageOfStrings(); public static void copyChars(char a1[], char a2[]) { ... }
- 개선 후:
int healthPoints, auxiliaryIndex, groupScore; int[] accounts; void convertStringsInXYZController(); void storeStringsInXYZController(); public static void copyCharacters(char source[], char destination[]) { ... }
- 혼란을 야기하는 이름 예시:
- 의미 있게 구분해라
컴파일러를 위한 코드가 아닌, 사람이 이해하기 쉬운 코드를 작성해야 한다. 연속된 숫자나 불용어를 사용하여 이름만으로는 의미를 구분하기 어렵게 만드는 것은 피해야 한다.- 지양해야 할 방식:
- 연속된 숫자 덧붙이기 (
a1
,a2
,a3
등) - 불용어 추가 (
ProductInfo
,ProductData
,Product
등)
- 연속된 숫자 덧붙이기 (
- 지양해야 할 단어:
noise word
(a, an, the 등), 의미가 비슷하거나 일반적인 단어 (info
,data
등)
- 지양해야 할 방식:
- 검색하기 쉬운 이름을 사용하라
이름 길이는 범위 크기에 비례해야 하며, 넓은 범위에서 사용되는 변수나 상수는 검색하기 쉽도록 더 명확하고 긴 이름을 사용하는 것이 좋다.- 예시 (매직 넘버 vs. 상수):
- 개선 전:
for (int j=0; j<34; j++){ s += (t[j]*4)/5; }
- 개선 후:
int realDaysPerIdealDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j=0; j < NUMBER_OF_TASKS; j++){ int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; }
- 개선 전:
- 발음 및 검색 용이성 예시:
- 개선 전:
class DtaRcrd102 { private Date genymdhms; }
- 개선 후:
class Customer { private Date generationTimestamp; }
- 개선 전:
- 예시 (매직 넘버 vs. 상수):
- 인코딩을 피하라
헝가리안 표기법, 접두사/접미사 기반 네이밍, 불필요한 축약어 등은 코드 가독성을 떨어뜨리고 유지보수를 어렵게 만든다. 현대적인 IDE는 타입 정보를 쉽게 제공하므로, 변수 이름에 타입을 인코딩할 필요가 없다.- 불필요한 접두어 사용 예시:
- 개선 전:
interface IShapeFactory { void print(); } class CShapeFactory implements IShapeFactory { private String m_name; // ... }
- 개선 후:
interface ShapeFactory { void print(); } class ShapeFactoryImpl implements ShapeFactory { private String name; // ... }
- 개선 전:
- 인터페이스와 구현 클래스 네이밍:
인터페이스 이름에I
를 붙이는 대신, 구현 클래스 이름에Impl
접미사를 붙이는 방식도 있다.
- 불필요한 접두어 사용 예시:
- 자신의 기억력을 자랑하지 마라
짧고 함축적인 이름보다는 명료하고 이해하기 쉬운 이름을 사용해야 한다. 넓은 범위에서 사용되는 변수나 함수는 문자 하나의 이름보다 의미를 담은 이름을 사용하는 것이 중요하다.- 지양:
문자 하나로 된 변수 이름 (l
,o
등), 불필요한 축약어 - 허용:
루프 변수i
,j
,k
(로컬 범위 내)
- 지양:
- 클래스 이름과 메서드 이름
- 클래스 이름:
명사나 명사구 (예:Customer
,WikiPage
,Account
,AddressParser
).Manager
,Processor
,Data
,Info
와 같이 모호한 이름은 피한다. - 메서드 이름:
동사나 동사구 (예:getName
,postPayment
,deletePage
,save
).name
,pay
와 같이 모호한 이름은 피하며, 접근자(getter), 변경자(setter), 조건자(is)에는 해당 접두사를 사용한다. - 생성자 중복 정의:
생성자를 중복 정의할 때는 정적 팩토리 메서드 패턴을 사용하여 객체 생성 의도를 명확하게 한다.
예:Complex.FromRealNumber(23.0)
- 클래스 이름:
- 기발한 이름은 피하라
재미있거나 기발한 이름보다는 명확하고 솔직한 이름을 사용해야 한다. 특정 문화권에서만 이해할 수 있는 농담이나 유머는 피하고, 누구나 이해하기 쉬운 이름을 선택한다.- 예:
HolyHandGrenade
보다는DeleteItems
- 예:
- 한 개념에 한 단어를 사용하라
일관성 있는 용어 사용은 코드의 이해도를 높이고 혼란을 줄여준다. 같은 개념을 지칭할 때 여러 단어를 혼용하지 않고 하나의 단어를 선택하여 꾸준히 사용해야 한다.- 예:
fetch
,retrieve
,get
등 유사한 의미의 단어를 혼용하지 않는다.
- 예:
- 말장난을 하지 마라
- 한 단어를 두 가지 목적으로 사용하지 마라.
- 예시
- 지금까지 구현한
add()
메서드는 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다. - 새로 작성하는 메서드는 집합에 값 하나를 추가한다. 이 메서드를
add
라 불러도 괜찮을까? - 새 메서드는 맥락이 다르기 때문에
insert
나append
라는 이름이 적당하다.
- 지금까지 구현한
- 해법 영역에서 가져온 이름을 사용하라
- 코드를 읽을 사람도 프로그래머라는 사실을 명심하라. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.
- 프로그래머에게 익숙한 기술 개념에는 기술 이름이 가장 적합하다.
- 문제 영역에서 가져온 이름을 사용하라
- 적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져온다.
- 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 구분할 줄 알아야 한다.
- 의미 있는 맥락을 추가하라
- 스스로 의미가 분명한 이름이 있다면, 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
- 예시
- 변경 전:
firstName, lastName, street, houseNumber, city, state, zipcode
- 변경 후:
addrFirstName, addrLastName, addrStreet, addrHouseNumber, addrCity, addrState, addrZipcode
- 변수가 좀 더 큰 구조에 속한다는 사실이 분명해진다.
- 변경 전:
- 불필요한 맥락을 없애라
- 일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서이다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다.
2. 🤔 이해가 어려운 부분
🔍 질문하기
-
생성자 중복 정의
-
어려웠던 부분: 정적 팩토리 메서드를 통해 생성자 중복 정의하는 방법과 실제 사용 예시.
-
이해한 점: 생성자 오버로딩의 단점을 극복하고, 객체 생성 의도를 명확하게 드러내기 위해 정적 팩토리 메서드를 사용하는 것이다.
-
궁금한 점: 정적 팩토리 메서드의 실제 사용 방법과 다양한 예시.
✅ 답변:
정적 팩토리 메서드는 클래스 내부에private
생성자를 만들고,public static
메서드를 통해 객체를 생성하여 반환하는 방식이다.개선 후
Person
클래스 (정적 팩토리 메서드 사용) 예시class Person { private String name; private int age; // private 생성자로 외부에서 직접 객체 생성 방지 private Person(String name, int age) { this.name = name; this.age = age; } // 정적 팩토리 메서드 1: 이름만 설정하는 경우 public static Person withName(String name) { return new Person(name, 0); // 기본 나이 설정 } // 정적 팩토리 메서드 2: 이름과 나이를 설정하는 경우 public static Person withNameAndAge(String name, int age) { return new Person(name, age); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } }
사용 방법
public class Main { public static void main(String[] args) { Person p1 = Person.withName("Alice"); // 이름만 설정 (나이: 0) Person p2 = Person.withNameAndAge("Bob", 25); // 이름과 나이 설정 System.out.println(p1); // Person{name='Alice', age=0} System.out.println(p2); // Person{name='Bob', age=25} } }
정적 팩토리 메서드
withName()
와withNameAndAge()
는 객체 생성 의도를 명확하게 드러내어 코드 가독성을 높인다.
-
-
인터페이스 클래스 이름과 구현 클래스 이름
- 이해한 부분: 인터페이스 이름은 역할을, 구현 클래스 이름은 구체적인 객체를 나타내도록 직관적이고 명확하게 지어야 하며, 인터페이스 이름에
I
접두사를 붙이지 않는다. - 이해 코드:
// 인터페이스: 역할을 표현 public interface UserService { void registerUser(String name); } // 구현 클래스: 구체적인 구현체를 표현 public class DefaultUserService implements UserService { public void registerUser(String name) { System.out.println("Registering user: " + name); } }
- 이해한 부분: 인터페이스 이름은 역할을, 구현 클래스 이름은 구체적인 객체를 나타내도록 직관적이고 명확하게 지어야 하며, 인터페이스 이름에
-
의미 있는 맥락 추가하기
-
어려웠던 부분: 함수를 작은 기능 단위로 쪼개어 맥락을 추가하는 개념과 기능 단위의 모호성.
-
궁금한 점: 함수 기능 단위의 기준과 좋은 함수 설계 방식.
✅ 답변:
**'의미 있는 맥락 추가하기'**는 함수를 작고 명확한 기능 단위로 분리하여, 각 함수가 하나의 책임만 수행하도록 만드는 것을 의미한다.
함수 기능 단위는 **'하나의 명확하게 정의된 작업'**으로 판단하며, 다음 원칙들을 참고할 수 있다:- 단일 책임 원칙 (SRP): 함수는 오직 하나의 책임만 가져야 한다.
- 함수 이름: 기능을 명확하게 설명해야 한다.
- 단일 역할: 하나의 함수는 '무엇'을 하는지를 명확하게 나타내야 한다.
- 코드 재사용성: 중복 코드를 제거하고 재사용 가능해야 한다.
- 낮은 의존성: 다른 함수에 과도하게 의존하지 않도록 해야 한다.
예시: "주문 처리" 기능을 "주문 접수", "결제 처리", "재고 관리", "배송 정보 생성"과 같이 작은 함수 단위로 분리하여 코드 가독성, 재사용성, 응집도를 높일 수 있다.
-
-
그릇된 정보는 피하고 의미 있게 구분하라.
-
어려웠던 부분: 그릇된 정보에 대한 기준의 주관성.
-
궁금한 점: 그릇된 정보를 피하기 위해 코드를 어느 수준까지 구분해야 하는가?
✅ 답변:
"그릇된 정보"는 코드 독자를 혼란스럽게 하거나, 코드의 실제 동작 방식과 다르게 오해하도록 유도하는 모든 것을 의미한다.
판단 기준은 상황에 따라 다르지만, 다음 가이드라인을 참고할 수 있다:- 오해의 소지가 있는 단어 피하기: 일반적이지 않은 약어, 특정 플랫폼/기술 용어, 예를 들어 배열인데도
accountList
처럼 List로 오해될 수 있는 이름 등. - 유사한 이름으로 혼란 야기하지 않기:
getActiveAccount
,getActiveAccounts
등 이름만으로 역할 구분이 어려운 경우. - 모호한 이름 사용하지 않기:
XYZControllerHandlingOfStrings
,copyChars(char a1[], char a2[])
등 기능 파악이 어려운 이름.
핵심: 코드 독자의 입장에서 "이 이름이 코드를 오해 없이 정확하게 이해하는 데 도움이 되는가?"를 기준으로 판단하며, 코드 리뷰를 통해 동료 개발자들과 함께 개선하는 것이 중요하다.
- 오해의 소지가 있는 단어 피하기: 일반적이지 않은 약어, 특정 플랫폼/기술 용어, 예를 들어 배열인데도
-
-
인코딩을 피하라.
-
어려웠던 부분: 인코딩의 정확한 개념.
-
이해한 점: 인코딩은 변수에 부가 정보를 덧붙여 표기하는 방법이다.
-
궁금한 점: 인코딩을 피하라는 권장과 달리, 일부 언어(C#)에서 인코딩 규칙을 권장하는 이유는 무엇인가?
✅ 답변:
과거 C와 같이 타입 검사가 약하거나 IDE 기능이 부족했던 환경에서는 인코딩이 유용했을 수 있다. 그러나 현대적인 언어와 IDE는 강력한 타입 시스템과 개발 지원 기능을 제공하므로, 인코딩은 가독성을 해칠 수 있다.
C#의 인코딩 규칙: 인터페이스 이름에I
접두사, private 멤버 변수에_
접두사 등을 권장하기도 하지만, 이는 과거 유산이며 최근에는 인코딩을 지양하고 의미 명확한 이름 짓기를 선호하는 추세이다.
결론: "클린 코드"는 최신 개발 트렌드를 반영하여 인코딩을 피하는 것을 권장하지만, 특정 언어나 프로젝트에서 인코딩 규칙이 강제된다면 그에 따르는 것이 중요하다.
-
3. 📚 참고 사항
📢 논의하기
-
관련 자료 공유
-
추가 자료:
-
생성자 중복 정의 (정적 팩토리 메서드)
-
개선 전: 생성자 이름이 동일하여 의미 파악이 어렵고, 생성자가 늘어날수록 관리하기 힘들어짐.
class Person { private String name; private int age; public Person(String name) { // 첫 번째 생성자 this.name = name; this.age = 0; } public Person(String name, int age) { // 두 번째 생성자 (오버로딩) this.name = name; this.age = age; } }
-
개선 후: 정적 팩토리 메서드를 사용하여 각 생성자의 의미를 명확히 하고,
new
연산자 없이 객체 생성을 제어하여 불필요한 객체 생성을 방지.class Person { private String name; private int age; private Person(String name, int age) { // private 생성자 this.name = name; this.age = age; } public static Person withName(String name) { // 정적 팩토리 메서드 1 return new Person(name, 0); } public static Person withNameAndAge(String name, int age) { // 정적 팩토리 메서드 2 return new Person(name, age); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } }
-
사용 예시
public class Main { public static void main(String[] args) { Person p1 = Person.withName("Alice"); Person p2 = Person.withNameAndAge("Bob", 25); System.out.println(p1); // Person{name='Alice', age=0} System.out.println(p2); // Person{name='Bob', age=25} } }
-
-
-
논의하고 싶은 주제
- 이름 길이는 범위 크기에 비례:
변수, 함수, 클래스 등의 이름 길이는 해당 이름의 범위 크기에 비례하여 결정해야 한다.- 범위 결정 기준:
- 좁은 범위 (로컬 변수, 짧은 함수): 짧고 간결한 이름.
- 넓은 범위 (전역 변수, 클래스 필드, 공용 API 메서드): 길고 명확하며 의미 있는 이름.
- 길이 기준: 범위가 좁을수록 짧은 이름, 범위가 넓을수록 긴 이름을 사용하는 것이 일반적이지만, 절대적인 기준은 없으며 팀/프로젝트 컨벤션에 따르는 것이 중요하다.
- 범위 결정 기준:
- 이름 길이는 범위 크기에 비례:
No Comments