
DRY와 WET 원칙은 각각 무슨 약어일까?
You can ask yourself "Haven't I written this before?" two times, but never three.
DRY를 따르면 코드가 어떻게 될까?
// utils/string.ts // 문자열 뒤에 접미사를 붙이는 함수입니다. 어디서나 쓸 수 있을 것 같아서 만들게 됩니다. export const addSuffix = (value: string, suffix: string) => `${value}${suffix}`; // utils/number.ts // 숫자에 천 단위 구분 기호를 넣는 함수입니다. export const withComma = (value: number) => value.toLocaleString('ko-KR'); // utils/price.ts // 위 두 함수를 조합해서 원화 표기를 만드는 함수입니다. export const formatKRW = (value: number) => addSuffix(withComma(value), '원');
addSuffix는 재사용될 것 같아서 만든 함수입니다. 하지만 실제로 쓰는 곳은 formatKRW 하나뿐이었고, 먼 미래에 addSuffix 와 애매하게 역할을 달리하는 함수를 발견하곤 합니다. DRY는 정말 '같은 코드를 쓰지 말라'는 뜻이었을까?
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
// 닉네임 길이 제한이라는 규칙을 한 곳에만 둡니다. export const MAX_NICKNAME_LENGTH = 12; // input, validation, 안내 문구가 모두 같은 값을 씁니다. <input maxLength={MAX_NICKNAME_LENGTH} />; const isValid = nickname.length <= MAX_NICKNAME_LENGTH; const message = `닉네임은 ${MAX_NICKNAME_LENGTH}자까지 입력할 수 있습니다.`;
그러면 AHA는 무엇이 다를까?

AHA Programming – Kent C. Dodds
Check out FAQ on this content, shortened version and extra resources for this talk at https://gitnation.com/contents/aha-programming Find the latest React talks & workshops at https://gitnation.com 🗓 Talk recording from React Summit Remote Edition 2020 See other React conferences by GitNation React Summit – https://reactsummit.com React Advanced London – https://reactadvanced.com React Day Berlin – https://reactday.berlin/ #AHA Programming Are you the kind of programmer who prefers to never see the same code in two places, or do you make liberal use of copy/paste? Many developers swear by the Don't Repeat Yourself (DRY) philosophy while others prefer to Write Everything Twice (WET). But which of these produces more maintainable codebases? I've seen both of these approaches lay waste to codebases and I have a new ideology I would like to propose to you: Avoid Hasty Abstractions (AHA). In this keynote, we'll talk about abstraction and how you can improve a codebase by applying and creating abstractions more thoughtfully as well as how to get yourself out of a mess of over or under-abstraction. Kent C. Dodds Kent is a world renowned speaker, teacher, and trainer and he's actively involved in the open source community as a maintainer and contributor of hundreds of popular npm packages. Kent is the creator of TestingJavaScript.com and he's an instructor on egghead.io and Frontend Masters. He's also a Google Developer Expert. Kent is happily married and the father of four kids. He likes his family, code, JavaScript, and React. Subscribe to our channel to see more React (Native) talks: https://www.youtube.com/channel/UCsFrt8oKNYXGspSlX9u6uXw/
성급한 추상화를 하면 어떤 모양이 될까?
ProductCard와 장바구니의 CartItem을 예로 들겠습니다. 둘 다 이미지, 이름, 가격을 보여 주고, 서로 다른 버튼이 붙습니다.// 상품 목록에서 쓰는 card입니다. function ProductCard({ product }: { product: Product }) { return ( <li className="card"> <img src={product.imageUrl} alt={product.name} /> <h3>{product.name}</h3> <p>{product.price.toLocaleString()}원</p> <WishButton id={product.id} /> </li> ); } // 장바구니에서 쓰는 card입니다. function CartItem({ item }: { item: CartItem }) { return ( <li className="card"> <img src={item.imageUrl} alt={item.name} /> <h3>{item.name}</h3> <p>{item.price.toLocaleString()}원</p> <QuantityStepper itemId={item.id} /> <button onClick={() => removeFromCart(item.id)}>삭제</button> </li> ); }
ItemCard 하나로 합쳤을 것입니다.요구사항 1. 검색 결과 화면에서 작은 card가 필요해졌습니다. 요구사항 2. 이벤트 상품은 가격을 숨겨야 했습니다. 요구사항 3. 주문서에서는 수량 조절 UI를 숨겨야 했습니다.
// 처음에는 variant 하나로 충분했습니다. interface ItemCardProps { item: Item; variant: 'product' | 'cart'; compact?: boolean; // 요구사항 1 : 검색 결과 화면용 작은 card hidePrice?: boolean; // 요구사항 2 : 이벤트 상품의 가격 숨김 showQuantity?: boolean; // 요구사항 3 : 주문서의 수량 조절 UI 숨김 onRemove?: () => void; } function ItemCard({ item, variant, compact, hidePrice, showQuantity, onRemove }: ItemCardProps) { return ( // 요구사항 1 : compact면 작은 card 스타일을 씁니다. <li className={compact ? 'card card--compact' : 'card'}> <img src={item.imageUrl} alt={item.name} /> <h3>{item.name}</h3> {/* 요구사항 2 : hidePrice면 가격을 그리지 않습니다. */} {!hidePrice && <p>{item.price.toLocaleString()}원</p>} {variant === 'product' && <WishButton id={item.id} />} {/* 요구사항 3 : showQuantity일 때만 수량 조절 UI를 그립니다. */} {variant === 'cart' && showQuantity && <QuantityStepper itemId={item.id} />} {variant === 'cart' && onRemove && <button onClick={onRemove}>삭제</button>} {item.soldOut && variant === 'product' && <SoldOutBadge />} {item.soldOut && variant === 'cart' && <p>품절된 상품입니다. 삭제해 주세요.</p>} </li> ); }
variant로 간단히 두가지 컴포넌트를 합쳐서 쓰는데, 요구사항들을 처리하다보니 compact, hidePrice, showQuantity등의 prop들이 늘어났습니다.ItemCard를 쓰는 쪽은 <ItemCard variant="cart" compact hidePrice /> 같은 조합을 매번 머릿속으로 따져 봐야 합니다. variant === 'cart'가 여기 저기서 보이는 것은, 한 component 안에 사실상 여러 책임의 component들이 들어 있다는 신호입니다. 
prefer duplication over the wrong abstraction
AHA로 다시 쓰면 어떻게 달라질까?
// 요구사항 1 : 가격 표기는 상품 목록과 장바구니가 같은 규칙을 씁니다. 같은 목적을 가지니 따로 유틸 함수로 뺍니다. export const formatPrice = (price: number) => `${price.toLocaleString('ko-KR')}원`; function ProductCard({ product }: { product: Product }) { return ( <li className="card"> <img src={product.imageUrl} alt={product.name} /> <h3>{product.name}</h3> <p>{formatPrice(product.price)}</p> {/* 요구사항 2 : 상품 목록에서는 품절이면 뱃지만 보여 줍니다. */} {product.soldOut && <SoldOutBadge />} {/* 요구사항 3 : 상품 목록에서는 찜하기 버튼이 필요합니다. */} <WishButton id={product.id} /> </li> ); } function CartItem({ item, onRemove }: { item: CartItemData; onRemove: () => void }) { return ( <li className="card"> <img src={item.imageUrl} alt={item.name} /> <h3>{item.name}</h3> <p>{formatPrice(item.price)}</p> {/* 요구사항 4 : 장바구니에서는 품절이면 삭제를 안내하는 문구를 보여 줍니다. */} {item.soldOut && <p>품절된 상품입니다. 삭제해 주세요.</p>} {/* 요구사항 5 : 장바구니에서는 수량 조절과 삭제가 필요합니다. */} <QuantityStepper itemId={item.id} /> <button onClick={onRemove}>삭제</button> </li> ); }
CartItem만 보면 됩니다.마무리
참고 자료
- Kent C. Dodds, AHA Programming: https://kentcdodds.com/blog/aha-programming
- Sandi Metz, The Wrong Abstraction: https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
- Cher Scarlett, Avoiding Hasty Abstractions (AHA programming): https://dev.to/cher/avoiding-hasty-abstractions-aha-programming-3d3b
kyu-log
-_Copy.png%253Ftable%253Dblock%2526id%253D39868e26-5a7c-8031-9aad-f5dd1e1e091a%2526cache%253Dv2%2526width%253D1200%26id%3D39868e26-5a7c-8055-a1aa-fd6b4b1719d2?table=block&id=3ec68e26-5a7c-8017-baee-db4aaf63c5f7&cache=v2&width=1200)