시니어 엔지니어에게 훔친 7가지 코딩 패턴

작성일:2026.07.03|수정일:2026.07.03|조회수:2

시니어 엔지니어에게 훔친 7가지 코딩 패턴

주니어 개발자들은 보통 시니어 엔지니어가 더 많은 트릭을 알고 있다고 생각한다.

대체로 그렇지 않다.

그들은 그저 다음 사람에게 더 적은 문제를 남긴다.

아픈 차이는 거기에 있다.

나도 예전에는 클린 코드란 우아한 코드라고 생각했다. 그러다 프로덕션 장애, 한밤중 롤백, 반쯤 끝난 데이터베이스 마이그레이션, 불분명한 오너십, 화난 고객, 아무도 건드리고 싶어 하지 않는 코드베이스를 버텨낸 엔지니어들과 일하게 되었다. 그들의 코드는 화려하지 않았다. 영리한 추상화로 가득하지도 않았다. 트위터에 올려서 사람들을 감탄시키려고 쓴 코드처럼 보이지도 않았다.

오히려 지루해 보였다.

그런데 요구사항이 바뀌었고, 그 코드는 살아남았다.

그때부터 나는 주의 깊게 보기 시작했다.

내가 시니어 엔지니어에게 훔친 최고의 패턴들은 특정 프레임워크에 묶인 것이 아니었다. React 패턴도, Node 패턴도, Java 패턴도, 클라우드 패턴도 아니었다. 그것은 판단의 패턴이었다. 코드를 쓰는 방식에 대한 패턴이었다.

여기 그 일곱 가지가 있다.

1. 조건 미로를 만들지 않고 일찍 반환한다

내가 훔친 첫 번째 패턴은 민망할 정도로 단순했다. 일찍 반환하기.

Return Early Instead of Building Conditional Mazes

예전의 나는 함수를 터널처럼 썼다. 조건 하나가 추가될 때마다 진짜 로직은 파일 안쪽으로 더 깊이 밀려 들어갔다. 먼저 사용자를 확인한다. 그다음 입력을 확인한다. 그다음 권한을 확인한다. 그다음 기능 플래그를 확인한다. 그다음 데이터베이스 결과를 확인한다. 실제 비즈니스 로직이 등장할 즈음에는 이미 여섯 단계 들여쓰기 아래에 있었고, 감정적으로도 지쳐 있었다.

시니어 엔지니어들은 그렇게 하지 않았다.

그들은 나쁜 경로를 먼저 제거했다.

TS
async function updateUserProfile(userId: string, input: ProfileInput) {
  const user = await getUser(userId);

  if (!user) {
    throw new NotFoundError("User not found");
  }

  if (!input.email) {
    throw new ValidationError("Email is required");
  }

  if (!user.canEditProfile) {
    throw new ForbiddenError("User cannot edit profile");
  }

  return saveProfile(user.id, input);
}

이건 화려하지 않다. “저는 가드 절을 썼습니다”라고 말해서 승진하는 사람은 없다. 하지만 이런 코드는 팀이 디버깅하는 방식을 바꾼다.

실패 경로가 눈에 보인다. 해피 패스가 묻히지 않는다. 함수는 장애 상황에서 개발자가 생각하는 순서대로 읽힌다. 무엇이 이 작업을 막을 수 있는가. 그리고 그 조건들이 사실이 아닐 때 무슨 일이 일어나는가.

나쁜 버전은 처음에는 별것 아닌 것처럼 보인다.

TS
async function updateUserProfile(userId: string, input: ProfileInput) {
  const user = await getUser(userId);

  if (user) {
    if (input.email) {
      if (user.canEditProfile) {
        return saveProfile(user.id, input);
      }
    }
  }

  throw new Error("Unable to update profile");
}

이 정도는 버틸 수 있을 것처럼 보인다. 그러다 누군가 계정 상태 검사를 추가한다. 누군가 조직 권한을 추가한다. 누군가 이메일 인증 규칙을 추가한다. 누군가 감사 로그를 추가한다. 이제 함수는 동굴이 된다.

중첩된 코드는 의미를 숨긴다. 읽는 사람이 머릿속에 너무 많은 상태를 들고 있게 만든다.

시니어 엔지니어는 코드를 읽는 비용이 코드를 쓰는 비용보다 크다는 것을 안다. 그래서 압박 속에서 코드를 이해하려는 사람을 위해 최적화한다.

물론 예외는 있다. 때로는 중첩 구조가 실제 계층을 표현한다. 파서, 트리 순회, 복잡한 워크플로에는 중첩된 로직이 필요할 수 있다. 이 패턴은 “절대 중첩하지 말라”가 아니다. 핵심은 이것이다. 독자가 요점에 도달하기 위해 문 다섯 개를 지나가게 만들지 말라는 것이다.

정리하면, 실패 경로를 일찍 제거해서 진짜 로직이 숨을 쉴 수 있게 하라.

2. 기술적 우연이 아니라 비즈니스 의미로 이름 짓는다

나쁜 이름은 미용상의 문제가 아니다.

Name the Business Meaning, Not the Technical Accident

그것은 디버깅 작업이다.

많은 개발자가 코드가 기술적으로 담고 있는 것에 따라 이름을 짓는다. data, result, item, payload, response, temp, obj, list, value 같은 이름이다. 코드는 컴파일된다. 기능도 동작한다. 모두가 넘어간다.

그러다 시스템이 커진다.

이제 data는 사용자다. 이 result는 결제 승인이다. 이 payload는 사실 비밀번호 재설정 요청이다. items는 인보이스인데, 미결제 인보이스만 의미한다. 단, 지난 분기에 필터가 바뀌어서 취소된 인보이스도 들어올 때가 있다.

시니어 엔지니어는 비즈니스 의미로 이름을 짓는다. 구현 세부사항은 바뀌어도 그 의미는 더 오래 살아남기 때문이다.

이 코드를 보자.

TS
const result = await getData(id);

if (result.status === "active") {
  await process(result);
}

그리고 이 코드를 보자.

TS
const subscription = await getSubscription(subscriptionId);

if (subscription.isBillable) {
  await chargeSubscription(subscription);
}

두 번째 버전은 단지 더 깔끔해 보이는 데서 끝나지 않는다. 다음 개발자에게 무엇이 중요한지 말해준다. 이것은 그냥 레코드가 아니다. 구독이다. 중요한 조건은 애매한 상태 체크가 아니다. 이 구독에 과금할 수 있는지 여부다.

이 차이가 시간을 아낀다.

나는 코드가 기술적으로는 맞지만 의미적으로 불분명해서 생긴 프로덕션 버그를 본 적이 있다. activeUsers라는 변수에는 정지되었지만 삭제되지는 않은 사용자가 포함되어 있었다. syncCustomer라는 함수는 인보이스도 생성했다. isValid라는 불리언은 “프론트엔드 검증을 통과했다”는 뜻이지, “저장해도 안전하다”는 뜻이 아니었다.

버그가 이름만으로 생긴 것은 아니다. 하지만 그 이름들이 잘못된 가정을 더 쉽게 만들었다.

좋은 이름은 오용에 마찰을 만든다.

TS
const usersEligibleForReactivation = await findUsersEligibleForReactivation();

그렇다. 길다.

좋다.

어떤 이름은 길어야 한다. 비즈니스 규칙이 구체적이기 때문이다. 짧고 모호한 이름은 더 단순한 이름이 아니다. 복잡성을 다른 사람의 기억 속으로 옮겨놓을 뿐이다.

물론 이름 짓기가 과시가 될 수도 있다. 모든 변수에 문단 같은 이름을 붙일 필요는 없다. 반복문의 인덱스는 i여도 된다. 작은 콜백은 단순한 이름이어도 된다. 목표는 연극적인 이름 짓기가 아니다. 비즈니스 위험을 품고 있는 개념에 제대로 이름을 붙이는 것이다.

정리하면, 시니어 엔지니어는 다음 개발자가 구현을 뒤져 의도를 역추적하지 않아도 되도록 코드에 이름을 붙인다.

3. 외부의 혼돈에 경계를 둔다

시니어 엔지니어는 외부 시스템이 계속 예의 바르게 행동하리라고 믿지 않는다.

Put Boundaries Around External Chaos

API는 바뀐다. 웹훅은 늦게 도착한다. 서드파티 필드는 사라진다. 결제 제공자는 이상한 엣지 케이스를 반환한다. 인증 토큰은 최악의 순간에 만료된다. 날짜 형식은 범죄 현장이 된다. “임시” 연동이 제품의 절반을 떠받치기 시작한다.

주니어 코드는 종종 그 혼돈이 여기저기 새어나가게 둔다.

TS
const userName = response.data.user_name;
const isActive = response.data.status === "ACTIVE";
const plan = response.data.subscription.plan_name;

처음에는 한 파일에서 시작된다. 그러다 퍼진다. 곧 코드베이스의 절반이 서드파티 응답의 정확한 형태를 알게 된다. 이는 곧 그 서드파티 벤더가 내부 아키텍처의 일부가 되었다는 뜻이다.

시니어 엔지니어는 보통 경계를 만든다.

TS
function mapBillingCustomer(response: BillingCustomerResponse): Customer {
  return {
    id: response.id,
    name: response.user_name,
    isBillable: response.status === "ACTIVE",
    planName: response.subscription?.plan_name ?? "Free"
  };
}

이것은 단순한 매핑이 아니다. 격리다.

시스템의 나머지 부분은 벤더가 user_name이라고 부르는지, customerName이라고 부르는지, profile.display_name이라고 부르는지, 혹은 그보다 더 이상한 이름을 쓰는지 신경 쓰지 않아야 한다. 시스템의 나머지 부분은 우리 팀이 소유한 형태를 받아야 한다.

이 패턴은 어디에나 적용된다.

나는 이걸 연동 변경 중에 어렵게 배웠다. 한 서드파티 API가 마이너 버전 업데이트에서 필드 이름을 바꿨다. 그 필드는 컨트롤러, 백그라운드 잡, 분석 이벤트, React 대시보드에서 직접 참조되고 있었다. 고치는 건 어렵지 않았다. 문제는 그 필드에 의존하는 모든 곳을 찾는 일이었다.

더 나은 버전이었다면 어댑터 테스트 하나에서 실패했을 것이다.

그 차이다.

물론 경계에는 비용이 있다. 앱이 아주 작다면 모든 것에 완전한 매핑 레이어를 두는 것은 과할 수 있다. 하지만 외부 데이터가 여러 기능을 가로지르기 시작하면, 바깥의 혼돈이 안쪽의 언어로 바뀌는 장소가 필요하다.

정리하면, 우리가 제어하지 않는 시스템이 우리가 제어하는 시스템의 형태를 정의하게 두지 말라.

4. 잘못된 상태가 숨어들기 어렵게 코드를 쓴다

한 시니어 엔지니어가 내 코드를 리뷰하다가 이렇게 말했다. “이 코드는 불가능한 케이스가 존재할 수 있게 두네요.”

Make Invalid States Boringly Hard

나는 그 말에 짜증이 났다. 기능은 동작했기 때문이다.

그리고 2주 뒤에 깨졌다.

그 코드는 모든 필드가 선택적인 사용자 객체를 가지고 있었다.

TS
type User = {
  id?: string;
  email?: string;
  role?: string;
  status?: string;
};

모든 필드가 선택적인 이유는 TypeScript가 불평하지 않게 만들기 위해서였다. 이것은 타입 안전성이 아니다. 항복이다.

이제 애플리케이션은 모든 곳에서 같은 불안한 질문을 해야 했다.

시니어 엔지니어는 모든 호출자가 말도 안 되는 상태를 방어하게 만들지 않는다. 상태를 더 정직하게 모델링한다.

TS
type DraftUser = {
  email: string;
  role: "admin" | "member";
};

type SavedUser = {
  id: string;
  email: string;
  role: "admin" | "member";
  status: "active" | "disabled";
};

이제 코드는 실제 차이를 표현할 수 있다. 초안 사용자는 저장된 사용자가 아니다.

너무 당연하게 들린다. 하지만 많은 버그는 서로 다른 두 상태를 같은 것으로 가장하는 코드에서 나온다.

결제는 그냥 결제가 아니다. 대기 중일 수도 있고, 승인되었을 수도 있고, 캡처되었을 수도 있고, 실패했을 수도 있고, 환불되었을 수도 있고, 분쟁 중일 수도 있다. 주문은 그냥 주문이 아니다. 장바구니일 수도 있고, 제출된 주문일 수도 있고, 이행된 주문일 수도 있고, 취소된 주문일 수도 있고, 반품된 주문일 수도 있다. 사용자는 그냥 사용자가 아니다. 초대된 사용자, 활성 사용자, 정지된 사용자, 삭제된 사용자, 인증 대기 중인 사용자일 수 있다.

이 상태들이 느슨하게 표현되면 버그는 반복된다.

누군가는 인증되지 않은 이메일 주소를 가진 사용자에게 메일을 보낸다. 누군가는 캡처되지 않은 결제를 환불한다. 누군가는 온보딩을 끝내지 않은 계정에 대시보드 액션을 보여준다. 누군가는 “이 단계 이후에는 존재한다”는 이유로 필드가 있다고 가정한다. 단, 어떤 코드 경로는 그 단계를 건너뛰었다.

더 나은 모델이 모든 검증을 없애지는 않는다. 다만 잘못된 사용을 더 일찍 보이게 만든다.

TS
type Payment =
  | { state: "pending"; id: string }
  | { state: "authorized"; id: string; authorizationId: string }
  | { state: "captured"; id: string; receiptId: string }
  | { state: "failed"; id: string; reason: string };

이제 영수증을 보내는 함수는 캡처된 결제를 요구할 수 있다.

TS
function sendReceipt(payment: Extract<Payment, { state: "captured" }>) {
  return emailReceipt(payment.receiptId);
}

이것은 타입을 숭배하자는 이야기가 아니다. 동적 언어에서도 검증, 생성자, 스키마, 팩토리, 명확한 런타임 체크로 같은 아이디어를 적용할 수 있다.

정확한 도구보다 중요한 것은 규율이다.

정리하면, 시니어 엔지니어는 잘못된 상태를 단순히 처리하지 않는다. 잘못된 상태가 숨을 곳을 줄이는 방식으로 코드를 설계한다.

5. 결정과 행동을 분리한다

내가 훔친 가장 유용한 패턴 중 하나는 결정과 행동을 분리하는 것이었다.

Separate Decisions From Actions

많은 코드는 이 둘을 섞는다.

TS
async function refundInvoice(invoiceId: string) {
  const invoice = await getInvoice(invoiceId);

  if (invoice.status !== "paid") {
    throw new Error("Invoice cannot be refunded");
  }

  if (invoice.refundedAt) {
    throw new Error("Invoice already refunded");
  }

  if (invoice.amount <= 0) {
    throw new Error("Invalid refund amount");
  }

  await paymentProvider.refund(invoice.paymentId);
  await markInvoiceRefunded(invoice.id);
  await sendRefundEmail(invoice.customerId);
}

이 코드가 끔찍한 것은 아니다. 충분히 읽을 만하다. 하지만 결정과 행동이 붙어 있다.

그러면 테스트가 필요 이상으로 무거워진다. 환불 가능 여부를 테스트하고 싶어도 결제 제공자, 데이터베이스 호출, 이메일 서비스, 그 밖에 함수가 건드리는 것들을 목 처리해야 할 수 있다. 그래서 테스트가 귀찮아진다. 그러면 팀은 테스트를 덜 쓴다. 그러면 환불 규칙이 깨진다.

시니어 엔지니어는 종종 결정을 순수 함수로 빼낸다.

TS
function getRefundEligibility(invoice: Invoice): RefundEligibility {
  if (invoice.status !== "paid") {
    return { allowed: false, reason: "Invoice is not paid" };
  }

  if (invoice.refundedAt) {
    return { allowed: false, reason: "Invoice is already refunded" };
  }

  if (invoice.amount <= 0) {
    return { allowed: false, reason: "Invalid refund amount" };
  }

  return { allowed: true };
}

그러면 행동은 더 단순해진다.

TS
async function refundInvoice(invoiceId: string) {
  const invoice = await getInvoice(invoiceId);
  const eligibility = getRefundEligibility(invoice);

  if (!eligibility.allowed) {
    throw new ValidationError(eligibility.reason);
  }

  await paymentProvider.refund(invoice.paymentId);
  await markInvoiceRefunded(invoice.id);
  await sendRefundEmail(invoice.customerId);
}

이제 비즈니스 규칙은 세상을 목 처리하지 않고도 테스트할 수 있다.

그게 진짜 이득이다.

이 패턴은 어디에나 나타난다. 권한 체크, 기능 접근, 가격 정책, 검증, 라우팅 결정, 재시도 로직, 알림 규칙, 워크플로 전환, 스케줄링 동작 같은 곳이다.

결정이 행동과 섞이면 규칙은 보기 어렵고 믿기 어려워진다. 결정이 분리되면 규칙은 검토 가능한 것이 된다.

물론 세 줄짜리 함수까지 전부 작은 아키텍처로 쪼갤 필요는 없다. 로직이 사소하고 바뀔 가능성이 낮다면 단순하게 둬도 된다. 하지만 어떤 결정이 비즈니스 위험을 품고 있다면, 그 결정에 이름과 테스트를 주는 편이 낫다.

정리하면, 결정은 그것이 제어하는 사이드 이펙트를 실행하지 않고도 쉽게 테스트할 수 있어야 한다.

6. 다음 사람에게 유용한 에러를 만든다

나쁜 에러 처리는 개발자의 에고가 변장한 모습이다.

Make Errors Useful to the Next Person

코드는 실패했고, 에러는 이렇게 말한다.

TXT
Something went wrong

누구에게 유용한가?

사용자에게도 아니다. 고객 지원팀에게도 아니다. 온콜 개발자에게도 아니다. 한밤중에 로그를 보는 사람에게도 아니다. 의미 있는 상태를 보여주려는 프론트엔드에게도 아니다. 실패한 요청을 트레이스와 연결하려는 백엔드 엔지니어에게도 아니다.

시니어 엔지니어는 에러를 커뮤니케이션으로 본다.

좋은 에러는 민감한 내부 정보를 노출하지 않는다. UI에 스택 트레이스를 뿌리지 않는다. 공격자에게 시스템이 어떻게 동작하는지 알려주지 않는다. 하지만 움직여야 하는 사람에게는 충분한 정보를 준다.

약한 API 에러는 이렇게 생겼다.

JSON
{
  "message": "Something went wrong"
}

조금 더 강한 에러는 이렇게 생길 수 있다.

JSON
{
  "code": "USER_EMAIL_ALREADY_EXISTS",
  "message": "A user with this email already exists.",
  "details": {
    "field": "email"
  },
  "requestId": "req_8f91a2"
}

중요한 것은 정확한 필드명이 아니다. 어떤 팀은 errorCode를 쓴다. 어떤 팀은 type을 쓴다. 어떤 팀은 problem details 형식을 쓴다. 어떤 팀은 request ID를 헤더에 넣는다. 괜찮다.

실수는 모호한 사람용 텍스트를 반환해놓고 모든 클라이언트, 로그, 대시보드, 지원 워크플로가 어떻게든 그걸 이해하리라고 기대하는 것이다.

나는 프론트엔드 코드가 에러 메시지를 이렇게 파싱하는 것을 본 적이 있다.

TS
if (error.message.includes("already exists")) {
  showEmailTakenError();
}

이 코드는 인질극이다.

백엔드가 문구를 바꾸면 프론트엔드가 깨진다. 번역자가 카피를 조정하면 로직이 깨진다. 다른 엔드포인트가 비슷한 텍스트를 반환하면 UI가 잘못된 상태를 보여준다.

텍스트는 사람을 위한 것이다. 코드는 시스템을 위한 것이다.

시니어 엔지니어는 로그에도 맥락을 넣는다.

TS
logger.warn("Refund rejected", {
  invoiceId,
  customerId,
  reason: eligibility.reason,
  requestId
});

모든 것을 로그로 남길 필요는 없다. 민감한 데이터를 로깅하는 것은 심각한 실수다. 비밀번호, 토큰, 개인정보, 결제 정보, 시크릿은 로그에 들어가면 안 된다. 하지만 유용한 식별자, 안전한 맥락, 구조화된 이유는 장애 대응을 추측 게임에서 추적 가능한 경로로 바꿀 수 있다.

정리하면, 에러는 다음 사람이 무엇이 실패했는지, 어디서 실패했는지, 어떤 증거가 그 실패를 연결하는지 이해할 수 있게 도와야 한다.

7. 데모가 아니라 diff에 맞춰 최적화한다

주니어 개발자는 보통 기능이 로컬에서 동작하게 만드는 데 최적화한다.

Optimize for the Diff, Not the Demo

시니어 엔지니어는 변경이 리뷰 가능하도록 최적화한다.

이 차이는 크다.

기능이 데모에서 완벽하게 동작해도 머지하기에는 위험할 수 있다. 코드가 너무 많은 영역을 건드릴 수 있다. diff가 리팩터링과 동작 변경을 섞고 있을 수 있다. 테스트가 해피 패스만 증명할 수 있다. 마이그레이션이 관련 없는 작업 안에 숨어 있을 수 있다. 설정 변경이 문서화되어 있지 않을 수 있다. 롤백 경로가 불분명할 수 있다.

데모는 동작한다.

시스템은 더 위험해진다.

시니어 엔지니어는 diff 단위로 생각한다. diff는 팀이 변화를 흡수하는 방식이기 때문이다.

깨끗한 diff는 이야기를 들려준다. 무엇이 바뀌었는지, 왜 바뀌었는지, 어떤 동작이 달라졌는지를 말한다. 지저분한 diff는 코드 리뷰를 고고학으로 만든다.

나쁜 버전은 이렇게 생겼다.

TXT
feat: update billing flow

- refactor invoice service
- rename payment fields
- update refund logic
- change dashboard UI
- add new webhook handler
- modify retry behavior
- fix customer status bug
- update tests

이건 pull request가 아니다. 인질극 메모다.

리뷰어는 어떤 변경이 필요한 것이고 어떤 변경이 기회주의적으로 끼어든 것인지 알 수 없다. 버그는 소음 속에 숨는다. 롤백은 무서워진다. 기능을 되돌리면 관련 없는 정리 작업까지 같이 되돌려야 하기 때문이다.

더 나은 접근은 지루하고 규율적이다.

TXT
PR 1: 동작 변경 없이 결제 필드 이름 바꾸기
PR 2: 환불 가능 여부 헬퍼와 테스트 추가하기
PR 3: 환불 가능 여부를 결제 플로에 연결하기
PR 4: 환불 사유를 보여주도록 대시보드 UI 업데이트하기
PR 5: 웹훅 재시도 동작 추가하기

타이핑만 세면 더 느리게 느껴진다.

리뷰, 디버깅, 롤백, 신뢰까지 세면 더 빠르다.

시니어 엔지니어는 리팩터링과 동작 변경을 섞는 데 조심스럽다. 그들도 리팩터링을 한다. 다만 제품 변경을 정리 작업 속에 숨기거나, 정리 작업을 제품 변경 속에 숨기는 일을 피한다.

이 패턴은 압박 속에서 가장 중요해진다. 장애 중에 작고 집중된 diff는 선물이다. 거대한 diff는 위협이다. 무언가 깨졌을 때 팀은 무엇이 바뀌었는지 알아야 한다. 모든 것이 바뀌었다면 아무도 아무것도 모른다.

정리하면, 코드는 내 컴퓨터에서 동작한다고 끝난 것이 아니다. 그 변경이 이해 가능하고, 리뷰 가능하고, 테스트 가능하고, 안전하게 되돌릴 수 있을 때 끝난 것이다.

패턴 아래의 패턴

이 일곱 가지 패턴은 표면적으로 서로 달라 보인다.

가드 절. 더 나은 이름. 경계. 더 안전한 상태. 분리된 결정. 유용한 에러. 리뷰 가능한 diff.

그 아래에는 모두 같은 아이디어가 있다.

놀라움을 줄이는 것.

시니어 엔지니어가 복잡성을 싫어하는 것은 아니다. 그들은 복잡성이 어딘가에는 살아야 한다는 것을 안다. 그것을 명확한 이름, 명시적인 경계, 정직한 타입, 집중된 함수, 유용한 에러, 작은 diff 안에 넣지 않으면, 복잡성은 사람들의 머릿속으로 퍼진다.

소프트웨어가 피곤해지는 곳은 거기다.

혼란스러운 코드베이스는 전달 속도만 늦추지 않는다. 개발자를 덜 자신 있게 만든다. 사람들은 무언가를 바꾸기 전에 망설인다. 시스템을 믿지 못해서 수동 테스트를 과하게 한다. Slack에서 같은 질문을 반복한다. 오래된 모듈을 피한다. 이해하지 못한 패턴을 복사한다. 작은 변경도 큰 불안과 함께 배포한다.

그건 그들이 약한 개발자라서가 아니다.

코드베이스가 모든 변경을 위험하게 느끼도록 만들고 있기 때문이다.

내가 함께 일한 최고의 시니어 엔지니어들은 똑똑해 보이려고 하지 않았다. 그들은 다음 변경이 덜 멍청해지도록 만들려고 했다.

그 차이다.

좋은 코드는 당신이 얼마나 많이 아는지를 보여주는 코드가 아니다.

좋은 코드는 다음 개발자가 추측해야 할 이유를 줄여주는 코드다.

댓글

댓글을 불러오는 중...