좋은 코드의 기준, 정말 '주관적'일까요?
좋은 코드의 기준이 있을까요? 깔끔한 코드? 읽기 쉬운 코드? 아름다운 코드?
좋은 코드의 기준이 이런 주관적인 것 밖에 없을까요?
팀장과 나의 코드 기준이 다른가요?
동료들의 코드를 리뷰할 때 나는 어떤 이야기를 해줄 수 있을까요?
본 밋업에서는 코드의 품질을 좀더 객관적으로 판단할 수 있는 기준들을 제시합니다.
그러나, 그 기준을 단순히 제시하는 것에 그치기보다는, 그 기준을 이끌어내기 위해 어떤 질문들을 던졌는지
어떤 사고 과정을 거쳤는지를 공유하고 각자가 스스로 고민하고 판단할 수 있는 기회를 제공하고자 합니다.
그래서, 각자가 다른 사고 과정을 거치더라도 보편적인 기준에 도달할 수 있다는 것을 함께 경험하기를 바랍니다.
사실 이 밋업을 듣는다고 해서 갑자기 매일 좋은 코드를 작성할 수 있게 되는 것은 아닙니다.
그러나, 나와 팀의 코드 품질을 높이기 위해 어떤 고민을 해야 하고, 어떤 질문을 던져야 하는지 알게 될 것입니다.
저는 늘 객관적이려 노력하는 사람이 주관적인 판단을 할 때 그 주관이 강력하고 믿을 만하다고 생각합니다. 저는 이 강의를 통해 여러분이 스스로 좀더 객관적인 시각을 갖게 되기를 기대합니다.
-박영록 지식공유자
✍️ 리팩토링, 클린코드… 그런데 “좋은 코드”는 뭘까?
“이 코드, 좀 더 깔끔하게 리팩토링해봐야 할 것 같은데…”
“이게 과연 클린 코드일까?”
“내가 짠 코드, 진짜 좋은 코드가 맞는 걸까?”
개발을 하면서 끊임없이 던지게 되는 질문들입니다.
저 역시도 여러 개발 서적과 강의(Clean Code, ‘내 코드가 그렇게 이상한가요?’ 등)를 통해 고민을 이어가고 있었고, 마침 “Simple Design: 25년차 개발자가 전하는 깔끔한 코드의 본질” 밋업이 열린다는 소식을 접하게 되었습니다.
솔직히 늘 이런 개발 밋업에 신청만 하고 떨어지기 일쑤였지만, 이번에는 운 좋게 동료 개발자들과 함께 참석하게 되었고, 제가 갖고 있던 고민들에 대해 현실적인 통찰을 얻을 수 있었습니다.
이 밋업이 특별했던 이유
단순히 "좋은 코드란 무엇인가?"에 대한 정답을 제시하는 것이 아니라,
“왜 그 기준이 좋은 코드의 기준이 될 수 있을까?”
“어떤 사고의 흐름으로 그 기준에 도달했는가?”
를 깊이 있게 따라가며, 각자 사고의 출발점은 달라도 결국 보편적인 기준에 도달할 수 있도록 설계된 내용이었습니다.
“객관적이려고 노력하는 사람의 주관적인 판단은, 믿을 만한 주관이다.”
코드 품질도 단순한 느낌이나 분위기로 판단할 게 아니라, 구체적인 질문과 논리적인 사고 과정을 통해 명확한 기준을 만들어가야 한다는 메시지가 인상 깊었습니다.
🛠 실무에서의 고민과 이번 밋업이 주는 통찰
최근에 운영 전 신규 프로젝트에서 기능 고도화 요구가 들어왔고, 저는 설계와 유지보수성에 더 초점을 두고자 했지만, 팀장님은 일정 우선으로 빠른 구현을 요청하셨습니다. 결국 저는 현실적인 선택을 할 수밖에 없었지만, 여전히 마음 속에는
“지금의 구조가 정말 괜찮은가?”
“나중에 유지보수하기 어려운 방향으로 가고 있진 않나?”
라는 고민이 남아 있었죠.
이번 밋업은 그런 저에게 실무 상황에서도 적용 가능한 실질적인 코드 판단 기준을 제시해주었고,
무엇보다 "좋은 코드에 대한 질문을 스스로 계속 던질 수 있게 만들어줬다"는 점에서 큰 의미가 있었습니다.
🎓 그래서 이 글은...
이 글은 단순히 밋업 후기나 정리 노트를 넘어서,
저처럼 "좋은 코드가 뭘까?"라는 질문 앞에 고민하는 개발자들에게
단순하지만 강력한 기준을 공유하고 싶어 작성하게 되었습니다.
이제 본격적으로 밋업에서 정리한 핵심 내용을 주제별로 정리해보겠습니다.
📚 Simple Design 좋은 코드를 작성하는 법
"객관적인 판단이란, 결국 객관적이려고 노력하는 사람의 주관적인 판단이다."
좋은 코드의 기준은?
코드 품질을 판단할 때 우리는 종종 다음과 같은 말을 듣습니다:
- "가독성이 좋아야 해"
- "중복은 없어야 해"
- "짧은 코드가 좋지"
- "구조가 잘 짜여 있어야 해"
그런데 이런 기준은 지나치게 많고, 주관적이며, 때로는 상급자의 취향으로 정의되기도 합니다.
핵심 메시지: 실무에서는 복잡한 기준보다, 단순하고 팀에 전파 가능한 기준이 더 중요합니다.
💡 1. 가독성이라는 개념의 위험성
“이 코드는 내가 읽기 어렵다 → 이 코드는 나쁘다”
→ 판단을 주관성에 맡기면 객관적 기준을 적용할 기회를 잃습니다.
비즈니스 로직이 복잡해서 코드도 복잡할 수 있습니다. 그럴 땐 협업을 통한 구조 개선이 필요하지, “가독성 나빠요” 한마디로 평가해선 안 됩니다.
- "가독성 좋은 코드"는 주관적인 개념.
- 문제의 복잡도 때문에 생기는 복잡함을 코드 탓으로 돌리면 안 된다.
- 가독성 대신 “이 코드가 누구에게 왜 필요한가”를 생각하자.
📌 핵심 메시지
=> 가독성은 판단 기준이 아닌 결과물일 수 있다. 객관적 기준(중복, 사이드이펙트, 구성요소 수 등)으로 판단하자.
💡 2. Simple Design 4원칙
Kent Beck / Martin Fowler가 정의한 심플 디자인의 핵심은 다음 4가지입니다.
1 | Passes the Tests | 모든 테스트를 통과한다 |
2 | Reveals Intention | 의도를 명확히 드러낸다 |
3 | No Duplication | 중복이 없다 |
4 | Fewest Elements | 구성요소가 최소화되어 있다 |
이 중 특히 중복 제거와 구성요소 최소화는 실무 적용도가 높습니다.
📌 중복이 없는 최소 구성요소가 Simple Design의 핵심.
💡 3. 중복 제거: Extract Method의 위험성
단순히 메서드로 분리하는 것이 무조건 좋은 것은 아닙니다.
잘못된 추출은 사이드 이펙트를 유발하고, 오히려 멱등성을 해칩니다.
📌 나쁜 예 (Side Effect)
public void calculateBillingAmount() {
billingAmount = 0;
for (Item item : orderItems) {
billingAmount += item.getPrice() * item.getQuantity();
}
calculateShippingFee(); // 이 메서드 안에서 billingAmount를 수정한다
billingAmount -= pointUsed;
}
private void calculateShippingFee() {
if (orderAmount < 10000) {
shippingFee = address.isIsland() ? 5000 : 3000;
billingAmount += shippingFee; // 사이드 이펙트
}
}
📌 좋은 예 (순수함수 사용)
public void calculateBillingAmount() {
billingAmount = orderItems.stream()
.mapToInt(item -> item.getPrice() * item.getQuantity())
.sum();
shippingFee = calculateShippingFee();
billingAmount += shippingFee;
billingAmount -= pointUsed;
}
private int calculateShippingFee() {
if (orderAmount < 10000) {
return address.isIsland() ? 5000 : 3000;
}
return 0;
}
💡 4. 중복의 확산: IF 조건 중복 → Shotgun Surgery
조건문은 코드 곳곳에 퍼지는 경향이 있습니다.
📌 나쁜 예: membership에 따른 조건 분기
if (membership.equals("VIP")) { /* A 처리 */ }
else if (membership.equals("REGULAR")) { /* B 처리 */ }
// 이런 조건이 여러 군데 흩어져 있다면...
→ membership 종류가 바뀌면 모든 조건문을 수정해야 하는 문제 발생
📌 좋은 예: 다형성 사용
abstract class Membership {
abstract int getDiscountRate();
}
class VIP extends Membership {
int getDiscountRate() { return 20; }
}
class Regular extends Membership {
int getDiscountRate() { return 10; }
}
class Order {
Membership membership;
int calculateDiscountedPrice(int price) {
return price * (100 - membership.getDiscountRate()) / 100;
}
}
💡 5. 평행 상속 계층 - 숨겨진 중복
📌 나쁜 예: 구조는 똑같은데 클래스만 다름
class UnixFile {}
class WindowsFile {}
class UnixFileHandler {}
class WindowsFileHandler {}
→ 유지보수 시 한 쪽만 고치고 다른 쪽은 빠뜨릴 위험.
📌 해결 방법: 전략 패턴 등 추상화
interface FileHandler {
void handle();
}
class UnixFileHandler implements FileHandler {
public void handle() { /* Unix 방식 */ }
}
class WindowsFileHandler implements FileHandler {
public void handle() { /* Windows 방식 */ }
}
💡 6. 시스템 전반의 중복: 클라이언트-서버 중복 제거 (DTO/Entity)
서버: Reservation, ReservationDTO
클라이언트: Reservation.ts, React Form
밸리데이션: Java + TypeScript
→ 이 모든 게 중복이다.
혹은 HTML over the wire (Hotwire) 같은 트렌드는
아예 클라이언트 코드를 최소화하고, 서버에서만 처리하게 합니다.
→ 이런 방식은 클라이언트-서버 간 중복을 근본적으로 제거해줍니다.
이처럼 중복이라는 건 단순한 if 문이나 for 문만의 문제가 아니라,
시스템 전체 구조에서도 반복되는 요소를 찾아내는 일입니다.
📌 해결책: OpenAPI 기반 자동 생성
- 서버 모델 → OpenAPI → TS 타입/폼/밸리데이션 자동 생성
- 라이브러리: openapi-generator, react-jsonschema-form, Django Form
→ 클라이언트-서버 중복 제거의 실질적인 해법
📌 중복이 많은 구조
// Entity
@Entity
public class Reservation {
private String name;
private LocalDate date;
}
// DTO
public class ReservationDTO {
private String name;
private LocalDate date;
}
// Form 필드, TypeScript 타입도 각각 따로 정의됨
📌 OpenAPI 스키마 기반 자동 생성 구조
- Java 모델 → OpenAPI spec → TS type + Form + 유효성 검사까지 자동화
- 예시: openapi-generator, react-jsonschema-form
💡 7. 구성 요소 줄이기 (Minimize Elements) : Spring Data REST
구성 요소란 클래스, 함수, 변수, 상속, 디렉토리, 라인 수, 테이블 수, 모든 것”
→ 가능한 줄일 수 있으면 줄여라
켄트 벡은 구성 요소를 클래스와 메소드로 정의했고,
마틴 파울러는 이를 더 일반화해서 ‘엘리먼트(Elements)’라고 했다.
즉, 우리가 셀 수 있는 모든 단위:
클래스, 함수, 분기, 상수, 상속 계층, 변수 등등
→ 가능한 한 모두 줄이자는 것이 핵심입니다.
📌 방해 요소들
- 과도한 일관성 강박
- 불필요한 components/ 디렉토리 생성 강요
- 예: 모든 디렉터리 구조를 동일하게 맞추려는 강박
- "Settings만 components 폴더가 있으니, Cart, Order도 다 있어야지?"
- 초기 구조 분리 집착
- “나중에 쓸 거니까 지금부터 DTO/Service 분리하자”
- “DTO / Service / Controller를 나눠야 클린하다”는 고정관념
- “초기부터 구조를 다 맞춰놔야지, 안 그러면 일관성 없다”는 주장
- 겉보기에 예쁜 디렉토리 구조
- 명령어 수만 늘어나고 생산성 저하
예를 들면:
어떤 프로젝트에서 React 구조를 보면
- 대부분 디렉토리는 단순한데
- Settings만 하위 컴포넌트가 많아 components 폴더로 구조화돼 있음
그러자 누군가가 말합니다:
“다른 디렉토리에도 다 components 폴더 만들어야 하는 거 아니야?”
→ 이렇게 해서 불필요한 구조가 덧붙여집니다.
또넌,
“나중엔 어차피 복잡해져서 DTO/Service/Controller 나눌 건데,
지금부터 구조 분리 안 하면 두 번 일하는 거 아니냐?”
이런 건 앞날을 너무 걱정한 나머지, 구조 강박에 빠지는 예입니다.
📌 반례 예시
jangbo 디렉토리 안에 React가 있어도 문제가 안 된다.
괜히 프론트/백엔드 동일 루트 구조 맞추려다 CLI 접근만 불편해진다.
“그 일관성, 진짜 필요한 일관성 맞나요?”
→ 이런 구조적 강박과 과도한 일관성 추구가 오히려
구성 요소를 최소화하려는 노력을 방해하는 요소입니다.
📌 DTO, Service, Controller 대신
Spring Data REST는 DTO, Controller, Service 없이도 CRUD API 제공
@RepositoryRestResource
public interface ReservationRepository extends JpaRepository<Reservation, Long> {}
→ CRUD API 자동 생성 + Swagger 문서화 + DTO 자동 직렬화
단, 복잡해질 경우 유지보수 어려움 주의.
📌 단점:
- 복잡한 비즈니스 로직에는 한계
- 커스터마이징은 어렵다
💡 8. 결론
- 코드 품질의 본질은 판단이 아닌 결과물
- 중복은 코드의 냄새이며, 제거는 실력이다.
- 구성 요소(클래스, 메서드, 필드, 테이블 등)를 줄여라.
- 잘못된 리팩토링은 품질을 망치며, 구조 강박은 생산성을 떨어뜨린다.
- 리팩토링을 하지 않아서 시간이 부족해지는 것이지, 시간이 없어서 리팩토링을 못하는 게 아니다
- Simple Design은 테스트와 리팩토링의 결과물이지 시작점이 아니다.
- 추상화와 자동화는 단순화 이후에 한다.
- Simple Design은 가볍지만, 깊은 철학이다.
💬 마무리하며 – “가독성이라는 단어는 해롭다”
“가독성이라는 표현은 해롭다.
사람들이 코드를 보다가 이해가 안 되면
‘이건 나쁜 코드야, 가독성이 안 좋아’라고 쉽게 판단해버리기 때문이다.”
밋업에서 이 말을 들었을 때, 저는 적잖은 충격을 받았습니다.
그동안 저 역시 “읽기 어렵다”는 이유만으로 코드를 쉽게 평가해버린 적이 있었고, 그게 객관적인 판단이라고 착각해왔던 건 아닐까 되돌아보게 되었어요.
실무 속의 나, 그리고 돌아본 내 코드
한참 실무에서 리팩토링을 담당했을 때가 떠올랐습니다.
지금 와서 생각해보면, 밋업에서 소개된 구조 강박이나 Extract Method의 오용, Long Method 분리의 오류 같은 사례들이 전부 저에게 해당되는 이야기였어요.
조금은 부끄러웠지만 동시에 스스로에게 이렇게 말하고 싶었습니다.
“그래도 나는 그만큼 더 나은 소프트웨어를 만들기 위해 끊임없이 고민해왔구나.”
복잡한 문제를 해결하려는 용기, 그리고 성장
실제로 우리는 종종 간단한 기능처럼 보이는 것조차 여러 상황이 얽혀 있어서 쉽게 해결하지 못하는 경험을 합니다.
하지만 그 속에서도 고객에게 가치를 줄 수 있다면, 끈기를 갖고 해결해나가는 경험이 정말 중요하다고 생각합니다.
협업 속에서도 주도적으로 문제를 정리하고 설득하고 개척해가는 사람,
그리고 서비스가 유지되고 성장하는 과정에서 코드와 함께 성장해가는 사람,
저는 그런 개발자가 되고 싶습니다.
🚀 나아가는 개발자
이번 밋업은 저에게 다시금 용기를 주었습니다.
지금 내 눈앞에 놓인 기술 부채를 피하지 않고,
작게라도 하나씩 해결해볼 수 있다는 자신감을 얻었고,
앞으로도 계속해서 고민하고 성장하는 개발자가 되어야겠다는 다짐을 하게 되었습니다.
좋은 코드는 하루아침에 완성되지 않지만,
좋은 개발자는 끊임없이 질문하고 변화하려는 사람이다.