hmk run dev
레거시를 만들지 않기 위한 습관 본문
"비즈니스는 속도가 생명이다." 초기 단계에서는 빠른 제품 출시와 시장 적응을 위해 효율성보다 결과를 우선시하기 쉽다. 이는 자연스럽게 기술 부채를 남기고, '레거시 코드'라 불리는 코드 뭉치나 비효율적인 아키텍처로 이어진다.
하지만, 레거시는 단순히 부정적인 요소가 아니다. 오히려 현재의 비즈니스를 지탱하는 '기반'이자 과거의 성과를 보여주는 흔적이기도 하다.
"우리는 이 레거시 덕분에 지금까지 성장할 수 있었다." 라는 생각도 가능하다.
레거시란 무엇인가?
레거시는 단순히 "오래된 코드"를 의미하지 않는다. 본질적으로 유지보수가 어렵고, 팀의 생산성을 저하시키는 시스템을 뜻한다. 이는 코드의 나이가 아니라 구조적 문제, 복잡성, 문서화 부족, 또는 기술 부채로 인해 발생하는 결과이다.
또 다른 관점에서, 어떤 이들은 코드는 배포된 순간부터 이미 레거시라고 말한다. 이는 코드가 시간이 지남에 따라 필연적으로 변화하는 요구사항, 새로운 기술 트렌드, 그리고 운영 환경에 적응해야 한다는 점에서 나온 의견이다.
이런 변화 속에서 코드가 유지보수 가능성을 잃거나, 확장성을 고려하지 않은 상태로 남을 때, "레거시"라는 꼬리표가 붙게 된다.
아마 개발자라면 커리어 내내 맞닥뜨리게 될 딜레마가 있습니다. 바로, "확장성을 우선시할 것인가, 아니면 현재 필요성에 집중할 것인가" 하는 선택입니다.
레거시를 만드는 개발습관엔 어떤 것들이 있을까?
레거시는 단순히 오래된 코드에서 비롯되는 것이 아니라, 개발 습관에서 시작되기도 합니다. 다음은 레거시를 만드는 대표적인 개발 습관들입니다:
- 단기적인 해결책(핫픽스)에 의존하는 개발
긴급한 문제 해결에만 집중하다 보면, 장기적으로 유지보수하기 어려운 코드가 쌓이게 됩니다. - 불명확한 코드 구조와 네이밍
가독성과 일관성이 떨어지는 코드와 애매한 네이밍은 팀원 간 의사소통을 어렵게 만듭니다. - 테스트와 자동화 부족
테스트가 없으면 코드 변경 시 안정성을 보장하기 어렵고, 자동화가 없으면 반복적인 작업이 증가합니다. - "일단 동작하면 됐다"는 태도
현재 문제를 해결하는 데만 집중하고, 코드의 확장성이나 품질을 간과하는 경우가 많습니다.
이와 같은 습관들은 초기에는 큰 문제가 되지 않을 수 있지만, 시간이 지나면서 점점 더 커다란 기술 부채를 쌓게 되어, 결국 조직의 생산성을 저하시키는 레거시로 이어질 수 있습니다.
물론 필자는 초기 스타트업에서 근무중인 개발자로써 빠른 비즈니스 속도에 맞춰 개발하다보면 위의 것들이 잘 지켜지지 않는 경우도 꽤 있다. 따라서, 비즈니스 요구와 코드 품질 사이의 균형을 찾는 것이 중요하다고 생각이든다.
레거시를 만들지 않기 위한 좋은 습관
1. 명확하고 일관된 코드 작성
- 의도를 드러내는 네이밍: 변수, 함수, 클래스의 이름이 그 역할과 목적을 명확히 드러내야 합니다.
- ❌ temp1, doStuff()
- ✅ userId, fetchUserData()
- 일관된 코드 스타일: 프로젝트 내에서 통일된 코딩 규칙(예: Prettier, ESLint)을 적용해 가독성을 유지합니다.
명확한 타입과 변수 네이밍
// 나쁜 예
const t1 = (a: any, b: any): any => a * b;
// 좋은 예
const calculateTotalPrice = (price: number, quantity: number): number => price * quantity;
명확한 인터페이스 정의
// 나쁜 예: 암시적으로 객체 사용
const createUser = (user: any) => {
console.log(user.name);
};
// 좋은 예: 명시적으로 인터페이스 사용
interface User {
name: string;
email: string;
}
const createUser = (user: User): void => {
console.log(user.name);
};
팀과 합의된 Eslint 규칙
//@deprecated 주석된 변수나 함수에 경고
'deprecation/deprecation': 'warn',
//특정 키워드로 시작하는 주석에 경고
'no-warning-comments': [
'warn',
{
terms: ['TODO', 'FIXME', 'TECH-DEBT', 'REFACTOR'],
location: 'start',
},
],
// 함수 중첩 깊이 제한
'max-depth': ['warn', 3],
// 함수 인자 개수 제한
'max-params': ['warn', 3],
// 중첩 콜백 제한
'max-nested-callbacks': ['warn', 3],
// 중첩된 삼항 연산자 방지
'no-nested-ternary': 'error',
2. 적절한 문서화와 주석 작성
- 코드 자체로 이해하기 어려운 부분은 간결한 주석을 남겨 미래의 개발자(혹은 본인) 가 쉽게 이해할 수 있도록 돕습니다.
- 시스템 아키텍처, 주요 로직의 흐름은 간단한 문서화로 기록해 두는 것이 좋습니다.
// 나쁜 예: 주석이 부족하거나 의도를 설명하지 않음
function calculate(a: number, b: number): number {
return a + b; // 더하기
}
// 좋은 예: 함수의 목적과 파라미터를 명확히 문서화
/**
* 두 숫자의 합계를 계산하는 함수
* @param a - 첫 번째 숫자
* @param b - 두 번째 숫자
* @returns 합계
*/
function calculateSum(a: number, b: number): number {
return a + b;
}
3. 테스트 주도 개발(TDD) 또는 테스트 우선 문화
- 자동화된 테스트 작성: 단위 테스트, 통합 테스트, E2E 테스트를 통해 코드 변경 시 안정성을 보장합니다.
- 테스트 커버리지 확인: 100% 커버리지가 목표는 아니지만, 주요 로직은 반드시 테스트를 작성합니다.
- 코드품질 보장, 안정적인 코드 변경지원, 문서화 역할, 코드 설계 개선 등등의 이점도 챙길 수 있다.
- 코드 수정 시 테스트 결과를 통해 빠르게 문제를 발견하고 해결할 수 있습니다.
// 테스트 작성
describe("calculateTotalPrice", () => {
it("should return the correct total price", () => {
expect(calculateTotalPrice(100, 2)).toBe(200);
});
});
// 코드 구현
const calculateTotalPrice = (price: number, quantity: number): number => price * quantity;
4. 점진적 리팩토링 습관화
- 기존 코드를 수정하거나 기능을 추가할 때, 작은 단위로 개선하는 리팩토링을 병행합니다.
- 기술 부채를 발견하면 무조건 미루기보다는 작은 빚부터 갚는 습관을 기릅니다.
- 예: 함수 분리, 반복되는 코드 제거, 오래된 의존성 업데이트 등.
- 예: 함수 분리, 반복되는 코드 제거, 오래된 의존성 업데이트 등.
// 리팩토링 전: 중복된 코드
function sendEmailToAdmin(user: { email: string }): string {
const email = user.email;
if (!email) return "Email not found";
// 이메일 전송 로직
return "Admin email sent";
}
function sendEmailToCustomer(user: { email: string }): string {
const email = user.email;
if (!email) return "Email not found";
// 이메일 전송 로직
return "Customer email sent";
}
// 리팩토링 후: 공통 로직 분리
function sendEmail(user: { email: string }, role: "Admin" | "Customer"): string {
const email = user.email;
if (!email) return "Email not found";
// 이메일 전송 로직
return `${role} email sent`;
}
5. 적절한 확장성과 단순성의 균형 유지
- 현재 필요한 기능에 집중하되, 확장 가능성을 염두에 둡니다.
- 예: YAGNI(You Aren't Gonna Need It) 원칙을 따르면서도, 인터페이스 설계나 모듈화를 통해 유연성을 확보합니다.
- 복잡한 아키텍처보다 KISS(Keep It Simple, Stupid) 원칙을 따라 단순한 설계를 우선합니다.
https://velog.io/@wngud4950/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C-3%EB%8C%80-%EC%9B%90%EC%B9%99-KISS-YAGNI-DRY
// 나쁜 예: 지나치게 복잡한 설계
function calculatePrice(price: number, tax: number, discount?: number, additionalFees?: number): number {
return price + tax - (discount || 0) + (additionalFees || 0);
}
// 좋은 예: 현재 요구사항에 맞는 단순한 설계
function calculatePrice(price: number, tax: number): number {
return price + tax;
}
6. 도메인 지식 공유
- 팀 내에서 도메인 지식을 공유하여 특정 개인에게 종속되지 않는 코드를 작성합니다.
- 지식이 공유될수록 코드 의도를 이해하고 유지보수하기 쉬워집니다.
팀 내 기술 공유 세션:
"지난주에 작업한 '상품 추천 알고리즘'은 사용자 행동 데이터를 기반으로 추천을 제공하는 로직입니다. 이를 통해 개인화 추천의 정확도를 15% 개선할 수 있었습니다."
코드 베이스 이해를 돕는 문서화
/**
* 사용자 추천 알고리즘
* @param userId - 추천할 사용자 ID
* @returns 추천 상품 배열
*/
function recommendProducts(userId: string): string[] {
// 추천 알고리즘 로직
return ["Product1", "Product2"];
}
7. 리팩토링 효율성 파악하기
git 명령어를 통해 수정이 빈번한 파일 정렬해서 출력하기
git log --name-only --pretty=format: |
sed -e 's|^pages/|^apps/mercedes/pages/|g' -e 's|^src/|^apps/mercedes/src/|g' |
grep -E '^(apps/mercedes/src|apps/mercedes/pages)' |
awk 'NR==FNR{a[$0];next} $0 in a' <(git ls-files) - |
sort | uniq -c | sort -nr
# --since="3 months ago"
git log --name-only --since="30 days ago" --pretty=format: |
sed -e 's|^pages/|^apps/mercedes/pages/|g' -e 's|^src/|^apps/mercedes/src/|g' |
grep -E '^(apps/mercedes/src|apps/mercedes/pages)' |
awk 'NR==FNR{a[$0];next} $0 in a' <(git ls-files) - |
sort | uniq -c | sort -nr
출력결과
687 apps/mercedes/src/service/ABTestingPolicyService.ts
322 apps/mercedes/pages/index.tsx
306 apps/mercedes/src/component/HomePage/index.tsx
301 apps/mercedes/pages/plans/[id].tsx
262 apps/mercedes/pages/plans/index.tsx
233 apps/mercedes/src/log/types/logNavigationTypes.ts
210 apps/mercedes/pages/_app.tsx
189 apps/mercedes/src/component/PlanDetailPage/SignUpInfoSection/index.tsx
165 apps/mercedes/src/component/MyOrderDetailPage/index.tsx
149 apps/mercedes/src/entity/plan.ts
113 apps/mercedes/src/component/TipsDetailPage/index.tsx
113 apps/mercedes/src/common/constants.ts
104 apps/mercedes/src/common/web-util.ts
100 apps/mercedes/src/component/StockAlarmPage/index.tsx
94 apps/mercedes/src/shared/components/surfaces/Card/PlanCard/PlanCard.tsx
86 apps/mercedes/src/component/PhoneContentsPage/index.tsx
83 apps/mercedes/src/component/ShorterPlanDetail/index.tsx
80 apps/mercedes/src/component/MyPagePageV2/BottomListSection.tsx
80 apps/mercedes/src/api/planOrder/planOrderLg.ts
78 apps/mercedes/pages/event.tsx
75 apps/mercedes/src/entity/api.ts
75 apps/mercedes/pages/tips/detail/[id].tsx
70 apps/mercedes/src/entity/mvnos.ts
70 apps/mercedes/src/component/InternetsPage/index.tsx
70 apps/mercedes/pages/calculator/plans-by-data/start.tsx
66 apps/mercedes/src/modules/MvnoCardList/MvnoCardList.module.scss
66 apps/mercedes/src/component/MyOrdersPage/OrderProgressFaqSection.tsx
66 apps/mercedes/src/common/http-util.ts
66 apps/mercedes/pages/tips.tsx
65 apps/mercedes/src/component/ReferralCodePage/indexV2.tsx
64 apps/mercedes/src/component/PlanDetailPage/PlanInfoSection/PlanInfoSection.module.scss
64 apps/mercedes/src/api/plan.ts
63 apps/mercedes/src/modules/Header/Header.tsx
63 apps/mercedes/src/component/PlanBridgePage/index.tsx
61 apps/mercedes/src/log/types/logObjectTypes.ts
59 apps/mercedes/src/component/InternetRequestPage/index.tsx
59 apps/mercedes/pages/referral/register.tsx
58 apps/mercedes/pages/login/kakao/index.tsx
57 apps/mercedes/src/modules/plans/layouts/PlanSearchFilters/PlanSearchFilters.tsx
57 apps/mercedes/src/entity/order/order.ts
57 apps/mercedes/pages/mypage/index.tsx
56 apps/mercedes/src/modules/BannerSlider/index.tsx
54 apps/mercedes/pages/community/questions/[...params].tsx
51 apps/mercedes/src/common/mvno-util.ts
51 apps/mercedes/pages/internets/index.tsx
50 apps/mercedes/src/modules/EventList/index.tsx
50 apps/mercedes/src/component/MyOrdersPage/OrderProgressSection.tsx
50 apps/mercedes/src/component/AffiliatePage/resultV3.tsx
50 apps/mercedes/pages/_document.tsx
49 apps/mercedes/src/component/PhoneContentsPage/phone-contents.ts
49 apps/mercedes/src/component/MvnoDetailPage/index.tsx
49 apps/mercedes/src/component/LoginPage/index.tsx
49 apps/mercedes/pages/_error.tsx
48 apps/mercedes/src/shared/utils/sendBehaviorLogScheduler.ts
48 apps/mercedes/src/component/TipsDetailPage/generate.tsx
48 apps/mercedes/src/component/PlanOrderPageV2/PlanOrderSerializer.ts
48 apps/mercedes/src/component/MyOrderDetailPage/ActivationRequestPage.tsx
'Front-End' 카테고리의 다른 글
커스텀훅을 이용해 리액트 modal 깔끔하게 다루기 (0) | 2024.10.26 |
---|---|
yarn plugin으로 우아하게 로깅하기 (0) | 2024.10.20 |
프론트엔드의 표준 (HTML, CSS, 크로스브라우징, 브라우저퍼포먼스, 웹 접근성) (0) | 2024.09.29 |
WebRTC의 동작원리 (0) | 2024.09.25 |
프론트엔드의 역사 (0) | 2024.09.22 |