From b49ee75e1f153f052393e36e4d5462c418491ed5 Mon Sep 17 00:00:00 2001 From: owonie Date: Sun, 19 Jan 2025 04:36:46 +0900 Subject: [PATCH 01/16] chore: create ch files --- .vitepress/ch.mts | 208 ++++++++++++++++ ch/code/community.md | 33 +++ ch/code/examples/code-directory.md | 77 ++++++ ch/code/examples/condition-name.md | 70 ++++++ ch/code/examples/error-boundary.md | 0 ch/code/examples/form-fields.md | 156 ++++++++++++ ch/code/examples/hidden-logic.md | 56 +++++ ch/code/examples/http.md | 86 +++++++ ch/code/examples/item-edit-modal.md | 106 ++++++++ ch/code/examples/login-start-page.md | 233 ++++++++++++++++++ ch/code/examples/magic-number-cohesion.md | 51 ++++ ch/code/examples/magic-number-readability.md | 59 +++++ ch/code/examples/submit-button.md | 73 ++++++ ch/code/examples/ternary-operator.md | 35 +++ ch/code/examples/use-bottom-sheet.md | 57 +++++ ch/code/examples/use-page-state-coupling.md | 95 +++++++ .../examples/use-page-state-readability.md | 101 ++++++++ ch/code/examples/use-user.md | 180 ++++++++++++++ ch/code/examples/user-policy.md | 108 ++++++++ ch/code/index.md | 81 ++++++ ch/code/start.md | 34 +++ ch/index.md | 28 +++ 22 files changed, 1927 insertions(+) create mode 100644 .vitepress/ch.mts create mode 100644 ch/code/community.md create mode 100644 ch/code/examples/code-directory.md create mode 100644 ch/code/examples/condition-name.md create mode 100644 ch/code/examples/error-boundary.md create mode 100644 ch/code/examples/form-fields.md create mode 100644 ch/code/examples/hidden-logic.md create mode 100644 ch/code/examples/http.md create mode 100644 ch/code/examples/item-edit-modal.md create mode 100644 ch/code/examples/login-start-page.md create mode 100644 ch/code/examples/magic-number-cohesion.md create mode 100644 ch/code/examples/magic-number-readability.md create mode 100644 ch/code/examples/submit-button.md create mode 100644 ch/code/examples/ternary-operator.md create mode 100644 ch/code/examples/use-bottom-sheet.md create mode 100644 ch/code/examples/use-page-state-coupling.md create mode 100644 ch/code/examples/use-page-state-readability.md create mode 100644 ch/code/examples/use-user.md create mode 100644 ch/code/examples/user-policy.md create mode 100644 ch/code/index.md create mode 100644 ch/code/start.md create mode 100644 ch/index.md diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts new file mode 100644 index 00000000..4fb7928d --- /dev/null +++ b/.vitepress/ch.mts @@ -0,0 +1,208 @@ +import { type DefaultTheme, defineConfig } from "vitepress"; + +export const ko = defineConfig({ + lang: "ko", + title: "Frontend Fundamentals", + description: "변경하기 쉬운 프론트엔드 코드를 위한 지침서", + lastUpdated: true, + themeConfig: { + logo: "/images/ff-symbol.svg", + nav: nav(), + + editLink: { + pattern: "https://github.com/toss/frontend-fundamentals/edit/main/:path", + text: "GitHub에서 수정하기" + }, + + outline: { + label: "페이지 내용" + }, + docFooter: { + prev: "이전 페이지", + next: "다음 페이지" + }, + lastUpdated: { + text: "마지막 업데이트" + }, + + sidebar: sidebar() + } +}); + +function nav(): DefaultTheme.NavItem[] { + return [{ text: "홈", link: "/" }]; +} + +function sidebar(): DefaultTheme.Sidebar { + return [ + { + text: "좋은 코드의 기준", + items: [ + { + text: "시작하기", + link: "/code/start" + }, + { + text: "변경하기 쉬운 코드", + link: "/code/" + }, + { + text: "커뮤니티", + items: [ + { + text: "소개", + link: "/code/community" + }, + { + text: "⭐ 좋은 논의 모아보기", + link: "https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22" + }, + { + text: "A vs B", + link: "https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b?discussions_q=is%3Aopen+category%3A%22A+vs+B%22+sort%3Adate_created" + }, + { + text: "자유로운 이야기", + link: "https://github.com/toss/frontend-fundamentals/discussions/categories/open-forum?discussions_q=is%3Aopen+sort%3Adate_created+category%3A%22Open+Forum%22" + } + ], + collapsed: true + } + ] + }, + { + text: "좋은 코드를 만드는 전략", + items: [ + { + text: "1. 가독성", + items: [ + { + text: "맥락 줄이기", + items: [ + { + text: "A. 같이 실행되지 않는 코드 분리하기", + link: "/code/examples/submit-button" + }, + { + text: "B. 구현 상세 추상화하기", + link: "/code/examples/login-start-page" + }, + { + text: "C. 로직 종류에 따라 합쳐진 함수 쪼개기", + link: "/code/examples/use-page-state-readability" + } + ], + collapsed: true + }, + { + text: "이름 붙이기", + items: [ + { + text: "A. 복잡한 조건에 이름 붙이기", + link: "/code/examples/condition-name" + }, + { + text: "B. 매직 넘버에 이름 붙이기", + link: "/code/examples/magic-number-readability" + } + ], + collapsed: true + }, + { + text: "위에서 아래로 읽히게 하기", + items: [ + { + text: "A. 시점 이동 줄이기", + link: "/code/examples/user-policy" + }, + { + text: "B. 삼항 연산자 단순하게 하기", + link: "/code/examples/ternary-operator" + } + ], + collapsed: true + } + ] + }, + + { + text: "2. 예측 가능성", + items: [ + { + text: "A. 이름 겹치지 않게 관리하기", + link: "/code/examples/http" + }, + { + text: "B. 같은 종류의 함수는 반환 타입 통일하기", + link: "/code/examples/use-user" + }, + { + text: "C. 숨은 로직 드러내기", + link: "/code/examples/hidden-logic" + } + ] + }, + { + text: "3. 응집도", + items: [ + { + text: "A. 함께 수정되는 파일을 같은 디렉토리에 두기", + link: "/code/examples/code-directory" + }, + { + text: "B. 매직 넘버 없애기", + link: "/code/examples/magic-number-cohesion" + }, + { + text: "C. 폼의 응집도 생각하기", + link: "/code/examples/form-fields" + } + ] + }, + { + text: "4. 결합도", + items: [ + { + text: "A. 책임을 하나씩 관리하기", + link: "/code/examples/use-page-state-coupling" + }, + { + text: "B. 중복 코드 허용하기", + link: "/code/examples/use-bottom-sheet" + }, + { + text: "C. Props Drilling 지우기", + link: "/code/examples/item-edit-modal" + } + ] + } + ] + } + ]; +} + +export const search: DefaultTheme.LocalSearchOptions["locales"] = { + root: { + translations: { + button: { + buttonText: "검색", + buttonAriaLabel: "검색" + }, + modal: { + backButtonTitle: "뒤로가기", + displayDetails: "더보기", + footer: { + closeKeyAriaLabel: "닫기", + closeText: "닫기", + navigateDownKeyAriaLabel: "아래로", + navigateText: "이동", + navigateUpKeyAriaLabel: "위로", + selectKeyAriaLabel: "선택", + selectText: "선택" + }, + noResultsText: "검색 결과를 찾지 못했어요.", + resetButtonTitle: "모두 지우기" + } + } + } +}; diff --git a/ch/code/community.md b/ch/code/community.md new file mode 100644 index 00000000..e8d0938d --- /dev/null +++ b/ch/code/community.md @@ -0,0 +1,33 @@ +--- +comments: false +--- + +# 커뮤니티 + +`Frontend Fundamentals`(FF)는 커뮤니티와 함께 좋은 코드의 기준을 만들어 가고 있어요. + +지금은 토스 프론트엔드 챕터가 운영하고 있어요. + +## 좋은 논의 모아보기 + +커뮤니티에서 있었던 좋은 논의를 살펴보세요. Frontend Fundamentals 문서에 나오는 내용을 넘어, 좋은 코드에 대한 생각을 넓힐 수 있어요. + +- [좋은 논의 모아보기](https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22) + +## 고민되는 코드에 대해 논의하기 + +고민되는 코드가 있다면 깃허브 디스커션에 글을 올려 보세요. +내 코드에 대해서 커뮤니티에서 다각도로 리뷰를 받을 수 있고, 좋은 코드의 기준에 대해 커뮤니티와 함께 고민할 수 있어요. + +많은 공감을 받은 사례는 직접 Frontend Fundamentals 문서에 올릴 수 있어요. 기여 방법은 추후 공개될 예정이에요. + +- [깃허브 디스커션에 글 올리기](https://github.com/toss/frontend-fundamentals/discussions) + +## 좋은 코드의 기준에 의견 더하기 + +좋은 코드의 기준에 대해 의견이 있거나, 새로운 의견을 더하고 싶다면 더 좋은 코드가 어떤 코드인지 투표하고, 의견을 남겨 보세요. +커뮤니티와 소통하며 더욱 풍부하고 깊이 있는 기준을 만들어 나가요. + +이 코드가 좋을까? 저 코드가 좋을까? 에 대해서 나만의 기준을 확립하는 계기가 될 수 있어요. + +- [A vs B에 올라온 코드 보기](https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b) diff --git a/ch/code/examples/code-directory.md b/ch/code/examples/code-directory.md new file mode 100644 index 00000000..8131ccbb --- /dev/null +++ b/ch/code/examples/code-directory.md @@ -0,0 +1,77 @@ +# 함께 수정되는 파일을 같은 디렉토리에 두기 + +
+ +
+ +프로젝트에서 코드를 작성하다 보면 Hook, 컴포넌트, 유틸리티 함수 등을 여러 파일로 나누어서 관리하게 돼요. 이런 파일들을 쉽게 만들고, 찾고, 삭제할 수 있도록 올바른 디렉토리 구조를 갖추는 것이 중요해요. + +함께 수정되는 소스 파일을 하나의 디렉토리에 배치하면 코드의 의존 관계를 명확하게 드러낼 수 있어요. 그래서 참조하면 안 되는 파일을 함부로 참조하는 것을 막고, 연관된 파일들을 한 번에 삭제할 수 있어요. + +## 📝 코드 예시 + +다음 코드는 프로젝트의 모든 파일을 모듈의 종류(Presentational 컴포넌트, Container 컴포넌트, Hook, 상수 등)에 따라 분류한 디렉토리 구조예요. + +```text +└─ src + ├─ components + ├─ constants + ├─ containers + ├─ contexts + ├─ remotes + ├─ hooks + ├─ utils + └─ ... +``` + +## 👃 코드 냄새 맡아보기 + +### 응집도 + +파일을 이렇게 종류별로 나누면 어떤 코드가 어떤 코드를 참조하는지 쉽게 확인할 수 없어요. 코드 파일 사이의 의존 관계는 개발자가 스스로 코드를 분석하면서 챙겨야 해요. +또한 더 이상 특정 컴포넌트나 Hook, 유틸리티 함수가 사용되지 않아서 삭제된다고 했을 때, 연관된 코드가 함께 삭제되지 못해서 사용되지 않는 코드가 남아있게 될 수도 있어요. + +프로젝트의 크기는 점점 커지기 마련인데, 프로젝트의 크기가 2배, 10배, 100배 커짐에 따라서 코드 사이의 의존관계도 크게 복잡해질 수 있어요. 디렉토리 하나가 100개가 넘는 파일을 담고 있게 될 수도 있어요. + +## ✏️ 개선해보기 + +다음은 함께 수정되는 코드 파일끼리 하나의 디렉토리를 이루도록 구조를 개선한 예시예요. + +```text +└─ src + │ // 전체 프로젝트에서 사용되는 코드 + ├─ components + ├─ containers + ├─ hooks + ├─ utils + ├─ ... + │ + └─ domains + │ // Domain1에서만 사용되는 코드 + ├─ Domain1 + │ ├─ components + │ ├─ containers + │ ├─ hooks + │ ├─ utils + │ └─ ... + │ + │ // Domain2에서만 사용되는 코드 + └─ Domain2 + ├─ components + ├─ containers + ├─ hooks + ├─ utils + └─ ... +``` + +함께 수정되는 코드 파일을 하나의 디렉토리 아래에 둔다면, 코드 사이의 의존 관계를 파악하기 쉬워요. + +예를 들어, 다음과 같이 한 도메인(`Domain1`)의 하위 코드에서 다른 도메인(`Domain2`)의 소스 코드를 참조한다고 생각해 볼게요. + +```typescript +import { useFoo } '../../../Domain2/hooks/useFoo' +``` + +이런 import 문을 만난다면 잘못된 파일을 참조하고 있다는 것을 쉽게 인지할 수 있게 돼요. + +또한, 특정 기능과 관련된 코드를 삭제할 때 한 디렉토리 전체를 삭제하면 깔끔하게 모든 코드가 삭제되므로, 프로젝트 내부에 더 이상 사용되지 않는 코드가 없도록 할 수 있어요. diff --git a/ch/code/examples/condition-name.md b/ch/code/examples/condition-name.md new file mode 100644 index 00000000..434fd150 --- /dev/null +++ b/ch/code/examples/condition-name.md @@ -0,0 +1,70 @@ +# 복잡한 조건에 이름 붙이기 + +
+ +
+ +복잡한 조건식이 특별한 이름 없이 사용되면, 조건이 뜻하는 바를 한눈에 파악하기 어려워요. + +## 📝 코드 예시 + +다음 코드는 상품 중에서 카테고리와 가격 범위가 일치하는 상품만 필터링하는 로직이에요. + +```typescript +const result = products.filter((product) => + product.categories.some( + (category) => + category.id === targetCategory.id && + product.prices.some( + (price) => price >= minPrice && price <= maxPrice + ) + ) +); +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +이 코드에서는 익명 함수와 조건이 복잡하게 얽혀 있어요. `filter`와 `some`, `&&` 같은 로직이 여러 단계로 중첩되어 있어서 정확한 조건을 파악하기 어려워졌어요. + +코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아서, 가독성이 떨어져요. [^1] + +[^1]: [프로그래머의 뇌](https://www.yes24.com/product/goods/105911017)에 따르면, 사람의 뇌가 한 번에 저장할 수 있는 정보의 숫자는 6개라고 해요. + +## ✏️ 개선해보기 + +다음 코드와 같이 조건에 명시적인 이름을 붙이면, 코드를 읽는 사람이 한 번에 고려해야 할 맥락을 줄일 수 있어요. + +```typescript +const matchedProducts = products.filter((product) => { + return product.categories.some((category) => { + const isSameCategory = category.id === targetCategory.id; + const isPriceInRange = product.prices.some( + (price) => price >= minPrice && price <= maxPrice + ); + + return isSameCategory && isPriceInRange; + }); +}); +``` + +명시적으로 같은 카테고리 안에 속해 있고, 가격 범위가 맞는 제품들로 필터링한다고 작성함으로써, 복잡한 조건식을 따라가지 않고도 코드의 의도를 명확히 드러낼 수 있어요. + +## 🔍 더 알아보기: 조건식에 이름을 붙이는 기준 + +언제 조건식이나 함수에 이름을 붙이고 분리하는 것이 좋을까요? + +### 조건에 이름을 붙이는 것이 좋을 때 + +- **복잡한 로직을 다룰 때**: 조건문이나 함수에서 복잡한 로직이 여러 줄에 걸쳐 처리되면, 이름을 붙여 함수의 역할을 명확히 드러내는 것이 좋아요. 이렇게 하면 코드 가독성이 높아지고, 유지보수나 코드 리뷰가 더 쉬워져요. + +- **재사용성이 필요할 때**: 동일한 로직을 여러 곳에서 반복적으로 사용할 가능성이 있으면, 변수나 함수를 선언해 재사용할 수 있어요. 이를 통해 코드 중복을 줄이고 유지보수가 더 쉬워져요. + +- **단위 테스트가 필요할 때**: 함수를 분리하면 독립적으로 단위 테스트를 작성할 수 있어요. 단위 테스트는 함수가 올바르게 동작하는지 쉽게 확인할 수 있어, 복잡한 로직을 테스트할 때 특히 유용해요. + +### 조건에 이름을 붙이지 않아도 괜찮을 때 + +- **로직이 간단할 때**: 로직이 매우 간단하면, 굳이 이름을 붙이지 않아도 돼요. 예를 들어, 배열의 요소를 단순히 두 배로 만드는 `arr.map(x => x * 2)`와 같은 코드는 이름을 붙이지 않아도 직관적이에요. + +- **한 번만 사용될 때**: 특정 로직이 코드 내에서 한 번만 사용되며, 그 로직이 복잡하지 않으면 익명 함수에서 직접 로직을 처리하는 것이 더 직관적일 수 있어요. diff --git a/ch/code/examples/error-boundary.md b/ch/code/examples/error-boundary.md new file mode 100644 index 00000000..e69de29b diff --git a/ch/code/examples/form-fields.md b/ch/code/examples/form-fields.md new file mode 100644 index 00000000..423da37c --- /dev/null +++ b/ch/code/examples/form-fields.md @@ -0,0 +1,156 @@ +# 폼의 응집도 생각하기 + +
+ +
+ +프론트엔드 개발을 하다 보면 Form으로 사용자에게 값을 입력받아야 하는 경우가 많아요. +Form을 관리할 때는 2가지의 방법으로 응집도를 관리해서, 함께 수정되어야 할 코드가 함께 수정되도록 할 수 있어요. + +## 필드 단위 응집도 + +필드 단위 응집은 개별 입력 요소를 독립적으로 관리하는 방식이에요. +각 필드가 고유의 검증 로직을 가지므로 변경이 필요한 범위가 줄어들어 특정 필드의 유지보수가 쉬워져요. +필드 단위의 응집도를 고려하여 설계하면, 각 필드의 검증 로직이 독립적이어서 다른 필드에 영향을 주지 않아요. + +```tsx +import { useForm } from "react-hook-form"; + +export function Form() { + const { + register, + formState: { errors }, + handleSubmit + } = useForm({ + defaultValues: { + name: "", + email: "" + } + }); + + const onSubmit = handleSubmit((formData) => { + // 폼 데이터 제출 로직 + console.log("Form submitted:", formData); + }); + + return ( +
+
+ + isEmptyStringOrNil(value) ? "이름을 입력해주세요." : "" + })} + placeholder="이름" + /> + {errors.name &&

{errors.name.message}

} +
+ +
+ { + if (isEmptyStringOrNil(value)) { + return "이메일을 입력해주세요."; + } + + if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { + return "유효한 이메일 주소를 입력해주세요."; + } + + return ""; + } + })} + placeholder="이메일" + /> + {errors.email &&

{errors.email.message}

} +
+ + +
+ ); +} + +function isNil(value: unknown): value is null | undefined { + return value == null; +} + +type NullableString = string | null | undefined; + +function isEmptyStringOrNil(value: NullableString): boolean { + return isNil(value) || value.trim() === ""; +} +``` + +## 폼 전체 단위 응집도 + +폼 전체 응집은 모든 필드의 검증 로직이 폼에 종속되는 방식이에요. 폼 전체에서의 흐름을 고려하여 설계되며, 변경 단위가 폼 단위로 발생할 때 고려해요. + +폼 전체 응집도를 높이면, 폼 전체의 검증이 한 곳에서 관리되어 로직이 간결해지고, 상태가 중앙 집중식으로 관리되므로 폼 전체 흐름을 이해하기 쉬워져요. 필드 간의 결합도가 높아지므로 폼의 재사용성은 떨어질 수 있어요. + +```tsx +import * as z from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; + +const schema = z.object({ + name: z.string().min(1, "이름을 입력해주세요."), + email: z + .string() + .min(1, "이메일을 입력해주세요.") + .email("유효한 이메일 주소를 입력해주세요") +}); + +export function Form() { + const { + register, + formState: { errors }, + handleSubmit + } = useForm({ + defaultValues: { + name: "", + email: "" + }, + resolver: zodResolver(schema) + }); + + const onSubmit = handleSubmit((formData) => { + // 폼 데이터 제출 로직 + console.log("Form submitted:", formData); + }); + + return ( +
+
+ + {errors.name &&

{errors.name.message}

} +
+ +
+ + {errors.email &&

{errors.email.message}

} +
+ + +
+ ); +} +``` + +## 필드 단위 vs. 폼 전체 단위 응집도 + +응집도를 높이려면 필드 단위와 폼 전체 단위 중 상황에 적합한 방식을 선택해야 해요. +필드 단위로 나누면 재사용성과 독립성이 높아지지만, 폼 전체 단위로 관리하면 일관된 흐름을 유지할 수 있어요. + +변경의 단위가 필드 단위인지 폼 전체 단위인지에 따라 설계를 조정해야 해요. + +### 필드 단위 응집도를 선택하면 좋을 때 + +- **독립적인 검증이 필요할 때**: 필드별로 복잡한 검증 로직이 필요하거나 비동기 검증이 필요한 경우예요. 이메일 형식 검사, 전화번호 유효성 검증, 아이디 중복 확인, 추천 코드 유효성 확인처럼 각 필드가 독립적이고 고유한 검증이 필요할 때 유용해요. +- **재사용이 필요할 때**: 필드와 검증 로직이 다른 폼에서도 동일하게 사용될 수 있는 경우예요. 공통 입력 필드들을 독립적으로 관리하고 재사용하고 싶을 때 좋아요. + +### 폼 전체 단위 응집도를 선택하면 좋을 때 + +- **단일 기능을 나타낼 때**: 모든 필드가 밀접하게 관련되어 하나의 완결된 기능을 구성하는 경우예요. 결제 정보나 배송 정보처럼 모든 필드가 하나의 비즈니스 로직을 이룰 때 유용해요. +- **단계별 입력이 필요할 때**: Wizard Form과 같이 스텝별로 동작하는 복잡한 폼의 경우예요. 회원가입이나 설문조사처럼 이전 단계의 입력값이 다음 단계에 영향을 주는 경우에 적합해요. +- **필드 간 의존성이 있을 때**: 여러 필드가 서로를 참조하거나 영향을 주는 경우예요. 비밀번호 확인이나 총액 계산처럼 필드 간 상호작용이 필요할 때 좋아요. diff --git a/ch/code/examples/hidden-logic.md b/ch/code/examples/hidden-logic.md new file mode 100644 index 00000000..57c19148 --- /dev/null +++ b/ch/code/examples/hidden-logic.md @@ -0,0 +1,56 @@ +# 숨은 로직 드러내기 + +
+ +
+ +함수나 컴포넌트의 이름, 파라미터, 반환 값에 드러나지 않는 숨은 로직이 있다면, 함께 협업하는 동료들이 동작을 예측하는 데에 어려움을 겪을 수 있어요. + +## 📝 코드 예시 + +다음 코드는 사용자의 계좌 잔액을 조회할 때 사용할 수 있는 `fetchBalance` 함수예요. 함수를 호출할 때마다 암시적으로 `balance_fetched`라는 로깅이 이루어지고 있어요. + +```typescript 4 +async function fetchBalance(): Promise { + const balance = await http.get("..."); + + logging.log("balance_fetched"); + + return balance; +} +``` + +## 👃 코드 냄새 맡아보기 + +### 예측 가능성 + +`fetchBalance` 함수의 이름과 반환 타입만을 가지고는 `balance_fetched` 라는 로깅이 이루어지는지 알 수 없어요. 그래서 로깅을 원하지 않는 곳에서도 로깅이 이루어질 수 있어요. + +또, 로깅 로직에 오류가 발생했을 때 갑자기 계좌 잔액을 가져오는 로직이 망가질 수도 있죠. + +## ✏️ 개선해보기 + +함수의 이름과 파라미터, 반환 타입으로 예측할 수 있는 로직만 구현 부분에 남기세요. + +```typescript +async function fetchBalance(): Promise { + const balance = await http.get("..."); + + return balance; +} +``` + +로깅을 하는 코드는 별도로 분리하세요. + +```tsx + +``` diff --git a/ch/code/examples/http.md b/ch/code/examples/http.md new file mode 100644 index 00000000..ca26378f --- /dev/null +++ b/ch/code/examples/http.md @@ -0,0 +1,86 @@ +# 이름 겹치지 않게 관리하기 + +
+ +
+ +같은 이름을 가지는 함수나 변수는 동일한 동작을 해야 해요. 작은 동작 차이가 코드의 예측 가능성을 낮추고, 코드를 읽는 사람에게 혼란을 줄 수 있어요. + +## 📝 코드 예시 + +어떤 프론트엔드 서비스에서 원래 사용하던 HTTP 라이브러리를 감싸서 새로운 형태로 HTTP 요청을 보내는 모듈을 만들었어요. +공교롭게 원래 HTTP 라이브러리와 새로 만든 HTTP 모듈의 이름은 `http`로 같아요. + +::: code-group + +```typescript [http.ts] +// 이 서비스는 `http`라는 라이브러리를 쓰고 있어요 +import { http as httpLibrary } from "@some-library/http"; + +export const http = { + async get(url: string) { + const token = await fetchToken(); + + return httpLibrary.get(url); + } +}; +``` + +```typescript [fetchUser.ts] +// http.ts에서 정의한 http를 가져오는 코드 +import { http } from "./http"; + +export async function fetchUser() { + return http.get("..."); +} +``` + +::: + +## 👃 코드 냄새 맡아보기 + +### 예측 가능성 + +이 코드는 기능적으로 문제가 없지만, 읽는 사람에게 혼란을 줄 수 있어요. `http.get`을 호출하는 개발자는 이 함수가 원래의 HTTP 라이브러리가 하는 것처럼 단순한 GET 요청을 보내는 것으로 예상하지만, 실제로는 토큰을 가져오는 추가 작업이 수행돼요. + +오해로 인해서 기대 동작과 실제 동작의 차이가 생기고, 버그가 발생하거나, 디버깅 과정을 복잡하고 혼란스럽게 만들 수 있어요. + +## ✏️ 개선해보기 + +서비스에서 만든 함수에는 라이브러리의 함수명과 구분되는 명확한 이름을 사용해서 함수의 동작을 예측 가능하게 만들 수 있어요. + +::: code-group + +```typescript [httpService.ts] +// 이 서비스는 `http`라는 라이브러리를 쓰고 있어요 +import { http as httpLibrary } from "@some-library/http"; + +// 라이브러리 함수명과 구분되도록 명칭을 변경했어요. +export const httpService = { + async getWithAuth(url: string) { + const token = await fetchToken(); + + // 토큰을 헤더에 추가하는 등 인증 로직을 추가해요. + return httpLibrary.get(url, { + headers: { Authorization: `Bearer ${token}` } + }); + } +}; +``` + +```typescript [fetchUser.ts] +// http.ts에서 정의한 http를 가져오는 코드 +import { httpService } from "./httpService"; + +export async function fetchUser() { + // 함수명을 통해 이 함수가 인증된 요청을 보내는 것을 알 수 있어요. + return await httpService.getWithAuth("..."); +} +``` + +::: + +이렇게 해서 함수의 이름을 봤을 때 동작을 오해할 수 있는 가능성을 줄일 수 있어요. +다른 개발자가 이 함수를 사용할 때, 서비스에서 정의한 함수라는 것을 인지하고 올바르게 사용할 수 있어요. + +또한, `getWithAuth`라는 이름으로 이 함수가 인증된 요청을 보낸다는 것을 명확하게 전달할 수 있어요. diff --git a/ch/code/examples/item-edit-modal.md b/ch/code/examples/item-edit-modal.md new file mode 100644 index 00000000..81123974 --- /dev/null +++ b/ch/code/examples/item-edit-modal.md @@ -0,0 +1,106 @@ +# Props Drilling 지우기 + +
+ +
+ +Props Drilling은 부모 컴포넌트와 자식 컴포넌트 사이에 결합도가 생겼다는 것을 나타내는 명확한 표시예요. 만약에 Drilling되는 프롭이 변경되면, 프롭을 참조하는 모든 컴포넌트가 변경되어야 하죠. + +## 📝 코드 예시 + +다음 코드는 사용자가 `item`을 선택할 때 사용하는 `` 컴포넌트예요. +사용자가 키워드를 입력해서 아이템 목록을 검색하고, 찾고 있었던 아이템을 선택하면 `onConfirm`이 호출돼요. + +사용자가 입력한 키워드는 `keyword`, 선택할 수 있는 아이템은 `items`, 추천 아이템의 목록은 `recommendedItems` 프롭으로 전달돼요. + +```tsx 2,9-10,12-13,39-42 +function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) { + const [keyword, setKeyword] = useState(""); + + // 다른 ItemEditModal 로직 ... + + return ( + + + {/* ... 다른 ItemEditModal 컴포넌트 ... */} + + ); +} + +function ItemEditBody({ + keyword, + onKeywordChange, + items, + recommendedItems, + onConfirm, + onClose +}) { + return ( + <> +
+ onKeywordChange(e.target.value)} + /> + +
+ + + ); +} + +// ... +``` + +## 👃 코드 냄새 맡아보기 + +### 결합도 + +이 컴포넌트는 부모인 `ItemEditModal`과 자식인 `ItemEditBody`, `ItemEditList` 등이 동일한 값인 `recommendedItems`, `onConfirm`, `keyword` 등을 프롭으로 공유하고 있어요. +부모 컴포넌트가 프롭을 그대로 자식 컴포넌트에게 넘겨주는 [Props Drilling](https://kentcdodds.com/blog/prop-drilling)이 발생하고 있어요. + +Props Drilling이 발생하면, 프롭을 불필요하게 참조하는 컴포넌트의 숫자가 많아져요. +그런데 프롭이 변경되면 프롭을 참조하는 모든 컴포넌트가 수정되어야 해요. + +예를 들어, 더 이상 아이템에 대한 추천 기능이 사라져서 `recommendedItems` 를 삭제해야 한다면, 연관된 모든 컴포넌트에서 삭제해야 하죠. +코드 수정범위가 필요 이상으로 넓고, 결합도가 높아요. + +## ✏️ 개선해보기 + +부모 컴포넌트가 자식 컴포넌트에게 그대로 프롭을 전달하는 Props Drilling을 제거해야 해요. 다음과 같이 조합(Composition) 패턴을 활용할 수 있어요. + +```tsx +function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) { + const [keyword, setKeyword] = useState(""); + + return ( + +
+ onKeywordChange(e.target.value)} + /> + +
+ +
+ ); +} +``` diff --git a/ch/code/examples/login-start-page.md b/ch/code/examples/login-start-page.md new file mode 100644 index 00000000..ac507d0a --- /dev/null +++ b/ch/code/examples/login-start-page.md @@ -0,0 +1,233 @@ +# 구현 상세 추상화하기 + +
+ +
+ +한 사람이 코드를 읽을 때 동시에 고려할 수 있는 총 맥락의 숫자는 제한되어 있다고 해요. +내 코드를 읽는 사람들이 코드를 쉽게 읽을 수 있도록 하기 위해서 불필요한 맥락을 추상화할 수 있어요. + +## 📝 코드 예시 1: LoginStartPage + +다음 `` 컴포넌트는 사용자가 로그인되었는지 확인하고, 로그인이 된 경우 홈으로 이동시키는 로직을 가지고 있어요. + +```tsx +function LoginStartPage() { + useCheckLogin({ + onChecked: (status) => { + if (status === "LOGGED_IN") { + location.href = "/home"; + } + } + }); + + /* ... 로그인 관련 로직 ... */ + + return <>{/* ... 로그인 관련 컴포넌트 ... */}; +} +``` + +### 👃 코드 냄새 맡아보기 + +#### 가독성 + +예시 코드에서는 로그인이 되었는지 확인하고, 사용자를 홈으로 이동시키는 로직이 추상화 없이 노출되어 있어요. 그래서 `useCheckLogin`, `onChecked`, `status`, `"LOGGED_IN"`과 같은 변수나 값을 모두 읽어야 무슨 역할을 하는 코드인지 알 수 있어요. + +이 코드와 더불어서, 실제로 로그인과 관련된 코드가 밑에 이어지는데요. 읽는 사람이 `LoginStartPage`가 무슨 역할을 하는지 알기 위해서 한 번에 이해해야 하는 맥락이 많아요. + +### ✏️ 개선해보기 + +사용자가 로그인되었는지 확인하고 이동하는 로직을 **HOC(Higher-Order Component)** 나 Wrapper 컴포넌트로 분리하여, 코드를 읽는 사람이 한 번에 알아야 하는 맥락을 줄여요. +그래서 코드의 가독성을 높일 수 있어요. + +또한, 분리된 컴포넌트 안에 있는 로직끼리 참조를 막음으로써, 코드 간의 불필요한 의존 관계가 생겨서 복잡해지는 것을 막을 수 있어요. + +#### 옵션 A: Wrapper 컴포넌트 사용하기 + +```tsx +function App() { + return ( + + + + ); +} + +function AuthGuard({ children }) { + const status = useCheckLoginStatus(); + + useEffect(() => { + if (status === "LOGGED_IN") { + location.href = "/home"; + } + }, [status]); + + return status !== "LOGGED_IN" ? children : null; +} + +function LoginStartPage() { + /* ... 로그인 관련 로직 ... */ + + return <>{/* ... 로그인 관련 컴포넌트 ... */}; +} +``` + +#### 옵션 B: HOC(Higher-Order Component) 사용하기 + +```tsx +function LoginStartPage() { + /* ... 로그인 관련 로직 ... */ + + return <>{/* ... 로그인 관련 컴포넌트 ... */}; +} + +export default withAuthGuard(LoginStartPage); + +// HOC 정의 +function withAuthGuard(WrappedComponent) { + return function AuthGuard(props) { + const status = useCheckLoginStatus(); + + useEffect(() => { + if (status === "LOGGED_IN") { + location.href = "/home"; + } + }, [status]); + + return status !== "LOGGED_IN" ? : null; + }; +} +``` + +## 📝 코드 예시 2: FriendInvitation + +다음 `` 컴포넌트는 클릭하면 사용자에게 동의를 받고 사용자에게 초대를 보내는 페이지 컴포넌트예요. + +```tsx 6-27,33 +function FriendInvitation() { + const { data } = useQuery(/* 생략.. */); + + // 이외 이 컴포넌트에 필요한 상태 관리, 이벤트 핸들러 및 비동기 작업 로직... + + const handleClick = async () => { + const canInvite = await overlay.openAsync(({ isOpen, close }) => ( + close(false)}> + 닫기 + + } + confirmButton={ + close(true)}> + 확인 + + } + /* 중략 */ + /> + )); + + if (canInvite) { + await sendPush(); + } + }; + + // 이외 이 컴포넌트에 필요한 상태 관리, 이벤트 핸들러 및 비동기 작업 로직... + + return ( + <> + + {/* UI를 위한 JSX 마크업... */} + + ); +} +``` + +### 👃 코드 냄새 맡아보기 + +#### 가독성 + +가독성을 지키려면 코드가 한 번에 가지고 있는 맥락이 적어야 해요. 하나의 컴포넌트가 가지고 있는 맥락이 다양하면 컴포넌트의 역할을 한눈에 파악하기 어려워져요. + +`` 컴포넌트는 실제로 사용자에게 동의를 받을 때 사용하는 자세한 로직까지 하나의 컴포넌트에 가지고 있어요. 그래서 코드를 읽을 때 따라가야 할 맥락이 많아서 읽기 어려워요. + +#### 응집도 + +사용자에게 동의를 받는 로직과 실제로 그 로직을 실행하는 로직인 ` + ); +} +``` + +`` 컴포넌트는 사용자를 초대하는 로직과 UI만 가지고 있으므로, 한 번에 인지해야 하는 내용을 적게 유지해서 가독성을 높일 수 있어요. 또한, 버튼과 클릭 후 실행되는 로직이 아주 가까이에 있어요. + +## 🔍 더 알아보기: 추상화 + +토스 기술 블로그의 [선언적인 코드 작성하기](https://toss.tech/article/frontend-declarative-code) 문서에서는 코드를 글로 비유해요. + +### 글에서 추상화 + +"왼쪽으로 10걸음 걸어라" 라고 하는 문장이 있어요. 여기에서 + +- “왼쪽”은 “북쪽을 바라보았을 때 90도 돌아간 위치” 를 추상화한 것이고, +- “90도”는 “한 번의 회전을 360등분한 각의 90배만큼 시초선에 대해 시계 반대 방향으로 돌아간 것” 을 추상화한 것이고, +- “시계 방향” 의 정의는 “북반구에서 해시계의 바늘이 돌아가는 방향” 을 추상화한 것이에요. + +비슷하게 "10걸음", "걸어라"와 같은 단어도 보다 구체적으로 표현할 수 있어요. 그래서 추상화 없이 그대로 문장을 나타낸다면, 다음과 같이 나타낼 수 있을 거예요. + +> 북쪽을 바라보았을 때 한 번의 회전을 360등분한 각의 90배만큼 북반구에서 해시계의 바늘이 돌아가는 방향으로 돌아서, 동물이 육상에서 다리를 이용해 움직이는 가장 빠른 방법보다 느린, 신체를 한 지점에서 다른 지점으로 옮겨가는 행위를 10번 반복해라 + +이 문장은 그대로 읽었을 때 어떤 의미인지 정확하게 파악하기 어려워요. + +### 코드에서 추상화 + +비슷하게 코드에서도 구현 상세를 지나치게 드러내는 경우, 이 코드가 어떤 역할을 하는지 정확하게 파악하기 어려워요. +한 번에 6~7개 정도의 맥락을 한 번에 고려해 가면서 읽을 수 있도록, 보다 작은 단위로 추상화하는 것이 필요해요. diff --git a/ch/code/examples/magic-number-cohesion.md b/ch/code/examples/magic-number-cohesion.md new file mode 100644 index 00000000..a668d32d --- /dev/null +++ b/ch/code/examples/magic-number-cohesion.md @@ -0,0 +1,51 @@ +# 매직 넘버 없애기 + +
+ +
+ +**매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요. + +예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나, +하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요. + +## 📝 코드 예시 + +다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요. + +```typescript 3 +async function onLikeClick() { + await postLike(url); + await delay(300); + await refetchPostLike(); +} +``` + +## 👃 코드 냄새 맡아보기 + +### 응집도 + +`300`이라고 하는 숫자를 애니메이션 완료를 기다리려고 사용했다면, 재생하는 애니메이션을 바꿨을 때 조용히 서비스가 깨질 수 있는 위험성이 있어요. +충분한 시간동안 애니메이션을 기다리지 않고 바로 다음 로직이 시작될 수도 있죠. + +같이 수정되어야 할 코드 중 한쪽만 수정된다는 점에서, 응집도가 낮은 코드라고도 할 수 있어요. + +::: info + +이 Hook은 [가독성](./magic-number-readability.md) 관점으로도 볼 수 있어요. + +::: + +## ✏️ 개선해보기 + +숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요. + +```typescript 1,5 +const ANIMATION_DELAY_MS = 300; + +async function onLikeClick() { + await postLike(url); + await delay(ANIMATION_DELAY_MS); + await refetchPostLike(); +} +``` diff --git a/ch/code/examples/magic-number-readability.md b/ch/code/examples/magic-number-readability.md new file mode 100644 index 00000000..5adf5ef1 --- /dev/null +++ b/ch/code/examples/magic-number-readability.md @@ -0,0 +1,59 @@ +# 매직 넘버에 이름 붙이기 + +
+ +
+ +**매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요. + +예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나, +하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요. + +## 📝 코드 예시 + +다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요. + +```typescript 3 +async function onLikeClick() { + await postLike(url); + await delay(300); + await refetchPostLike(); +} +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +이 코드는 `delay` 함수에 전달된 `300`이라고 하는 값이 어떤 맥락으로 쓰였는지 알 수 없어요. +원래 코드를 작성한 개발자가 아니라면, 어떤 목적으로 300ms동안 기다리는지 알 수 없죠. + +- 애니메이션이 완료될 때까지 기다리는 걸까? +- 좋아요 반영에 시간이 걸려서 기다리는 걸까? +- 테스트 코드였는데, 깜빡하고 안 지운 걸까? + +하나의 코드를 여러 명의 개발자가 함께 수정하다 보면 의도를 정확히 알 수 없어서 코드가 원하지 않는 방향으로 수정될 수도 있어요. + +::: info + +이 Hook은 [응집도](./magic-number-cohesion.md) 관점으로도 볼 수 있어요. + +::: + +## ✏️ 개선해보기 + +숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요. + +```typescript 1,5 +const ANIMATION_DELAY_MS = 300; + +async function onLikeClick() { + await postLike(url); + await delay(ANIMATION_DELAY_MS); + await refetchPostLike(); +} +``` + +## 🔍 더 알아보기 + +매직 넘버는 응집도 관점에서도 살펴볼 수 있어요. [매직 넘버 없애서 응집도 높이기](./magic-number-cohesion.md) 문서도 참고해 보세요. diff --git a/ch/code/examples/submit-button.md b/ch/code/examples/submit-button.md new file mode 100644 index 00000000..4903e181 --- /dev/null +++ b/ch/code/examples/submit-button.md @@ -0,0 +1,73 @@ +# 같이 실행되지 않는 코드 분리하기 + +
+ +
+ +동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면, 동작을 한눈에 파악하기 어려워요. +구현 부분에 많은 숫자의 분기가 들어가서, 어떤 역할을 하는지 이해하기 어렵기도 해요. + +## 📝 코드 예시 + +다음 `` 컴포넌트는 사용자의 권한에 따라서 다르게 동작해요. + +- 사용자의 권한이 보기 전용(`"viewer"`)이면, 초대 버튼은 비활성화되어 있고, 애니메이션도 재생하지 않아요. +- 사용자가 일반 사용자이면, 초대 버튼을 사용할 수 있고, 애니메이션도 재생해요. + +```tsx +function SubmitButton() { + const isViewer = useRole() === "viewer"; + + useEffect(() => { + if (isViewer) { + return; + } + showButtonAnimation(); + }, [isViewer]); + + return isViewer ? ( + Submit + ) : ( + + ); +} +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +`` 컴포넌트에서는 사용자가 가질 수 있는 2가지의 권한 상태를 하나의 컴포넌트 안에서 한 번에 처리하고 있어요. +그래서 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아요. + +예를 들어, 다음 코드에서 파란색은 사용자가 보기 전용 권한(`'viewer'`)을 가지고 있을 때, 빨간색은 일반 사용자일 때 실행되는 코드예요. +동시에 실행되지 않는 코드가 교차되어서 나타나서 코드를 이해할 때 부담을 줘요. + +![](../../images/examples/submit-button.png) + +## ✏️ 개선해보기 + +다음 코드는 사용자가 보기 전용 권한을 가질 때와 일반 사용자일 때를 완전히 나누어서 관리하도록 하는 코드예요. + +```tsx +function SubmitButton() { + const isViewer = useRole() === "viewer"; + + return isViewer ? : ; +} + +function ViewerSubmitButton() { + return Submit; +} + +function AdminSubmitButton() { + useEffect(() => { + showAnimation(); + }, []); + + return ; +} +``` + +- `` 코드 곳곳에 있던 분기가 단 하나로 합쳐지면서, 분기가 줄어들었어요. +- ``과 `` 에서는 하나의 분기만 관리하기 때문에, 코드를 읽는 사람이 한 번에 고려해야 할 맥락이 적어요. diff --git a/ch/code/examples/ternary-operator.md b/ch/code/examples/ternary-operator.md new file mode 100644 index 00000000..2eb7ed7c --- /dev/null +++ b/ch/code/examples/ternary-operator.md @@ -0,0 +1,35 @@ +# 삼항 연산자 단순하게 하기 + +
+ +
+ +삼항 연산자를 복잡하게 사용하면 조건의 구조가 명확하게 보이지 않아서 코드를 읽기 어려울 수 있어요. + +## 📝 코드 예시 + +다음 코드는 `A조건`과 `B조건`에 따라서 `"BOTH"`, `"A"`, `"B"` 또는 `"NONE"` 중 하나를 `status`에 지정하는 코드예요. + +```typescript +const status = + (A조건 && B조건) ? "BOTH" : (A조건 || B조건) ? (A조건 ? "A" : "B") : "NONE"; +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +이 코드는 여러 삼항 연산자가 중첩되어 사용되어서, 정확하게 어떤 조건으로 값이 계산되는지 한눈에 파악하기 어려워요. + +## ✏️ 개선해보기 + +다음과 같이 조건을 `if` 문으로 풀어서 사용하면 보다 명확하고 간단하게 조건을 드러낼 수 있어요. + +```typescript +const status = (() => { + if (A조건 && B조건) return "BOTH"; + if (A조건) return "A"; + if (B조건) return "B"; + return "NONE"; +})(); +``` diff --git a/ch/code/examples/use-bottom-sheet.md b/ch/code/examples/use-bottom-sheet.md new file mode 100644 index 00000000..931132c7 --- /dev/null +++ b/ch/code/examples/use-bottom-sheet.md @@ -0,0 +1,57 @@ +# 중복 코드 허용하기 + +
+ +
+ +개발자로서 여러 페이지나 컴포넌트에 걸친 중복 코드를 하나의 Hook이나 컴포넌트로 공통화하는 경우가 많아요. +중복 코드를 하나의 컴포넌트나 Hook으로 공통화하면, 좋은 코드의 특징 중 하나인 응집도를 챙겨서, 함께 수정되어야 할 코드들을 한꺼번에 수정할 수 있어요. + +그렇지만, 불필요한 결합도가 생겨서, 공통 컴포넌트나 Hook을 수정함에 따라 영향을 받는 코드의 범위가 넓어져서, 오히려 수정이 어려워질 수도 있어요. + +처음에는 비슷하게 동작한다고 생각해서 공통화한 코드가, 이후 페이지마다 다른 특이한 요구사항이 생겨서, 점점 복잡해질 수 있어요. +동시에 공통 코드를 수정할 때마다, 그 코드에 의존하는 코드들을 일일이 제대로 테스트해야 해서, 오히려 코드 수정이 어려워지기도 하죠. + +## 📝 코드 예시 + +아래와 같이 점검 정보를 인자로 받아서, 점검 중이라면 점검 바텀시트를 열고, 사용자가 알림 받기에 동의하면 이를 로깅하고, 현재 화면을 닫는 Hook을 살펴볼게요. + +```typescript +export const useOpenMaintenanceBottomSheet = () => { + const maintenanceBottomSheet = useMaintenanceBottomSheet(); + const logger = useLogger(); + + return async (maintainingInfo: TelecomMaintenanceInfo) => { + logger.log("점검 바텀시트 열림"); + const result = await maintenanceBottomSheet.open(maintainingInfo); + if (result) { + logger.log("점검 바텀시트 알림받기 클릭"); + } + closeView(); + }; +}; +``` + +이 코드는 여러 페이지에서 반복적으로 사용되었기에 공통 Hook으로 분리되었어요. + +## 👃 코드 냄새 맡아보기 + +### 결합도 + +이 Hook은 여러 페이지에서 반복적으로 보이는 로직이기에 공통화되었어요. 그렇지만 앞으로 생길 수 있는 다양한 코드 변경의 가능성을 생각해볼 수 있어요. + +- 만약에 페이지마다 로깅하는 값이 달라진다면? +- 만약에 어떤 페이지에서는 점검 바텀시트를 닫더라도 화면을 닫을 필요가 없다면? +- 바텀시트에서 보여지는 텍스트나 이미지를 다르게 해야 한다면? + +위 Hook은 이런 코드 변경사항에 유연하게 대응하기 위해서 복잡하게 인자를 받아야 할 거예요. +이 Hook의 구현을 수정할 때마다, 이 Hook을 쓰는 모든 페이지들이 잘 작동하는지 테스트도 해야 할 것이고요. + +## ✏️ 개선해보기 + +다소 반복되어 보이는 코드일지 몰라도, 중복 코드를 허용하는 것이 좋은 방향일 수 있어요. + +함께 일하는 동료들과 적극적으로 소통하며 점검 바텀시트의 동작을 정확하게 이해해야 해요. +페이지에서 로깅하는 값이 같고, 점검 바텀시트의 동작이 동일하고, 바텀시트의 모양이 동일하다면, 그리고 앞으로도 그럴 예정이라면, 공통화를 통해 코드의 응집도를 높일 수 있어요. + +그렇지만 페이지마다 동작이 달라질 여지가 있다면, 공통화 없이 중복 코드를 허용하는 것이 더 좋은 선택이에요. diff --git a/ch/code/examples/use-page-state-coupling.md b/ch/code/examples/use-page-state-coupling.md new file mode 100644 index 00000000..17b8cf15 --- /dev/null +++ b/ch/code/examples/use-page-state-coupling.md @@ -0,0 +1,95 @@ +# 책임을 하나씩 관리하기 + +
+ +
+ +쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 나누지 마세요. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 돼요. + +## 📝 코드 예시 + +다음 `usePageState()` Hook은 페이지 전체의 URL 쿼리 파라미터를 한 번에 관리해요. + +```typescript +import moment, { Moment } from "moment"; +import { useMemo } from "react"; +import { + ArrayParam, + DateParam, + NumberParam, + useQueryParams +} from "use-query-params"; + +const defaultDateFrom = moment().subtract(3, "month"); +const defaultDateTo = moment(); + +export function usePageState() { + const [query, setQuery] = useQueryParams({ + cardId: NumberParam, + statementId: NumberParam, + dateFrom: DateParam, + dateTo: DateParam, + statusList: ArrayParam + }); + + return useMemo( + () => ({ + values: { + cardId: query.cardId ?? undefined, + statementId: query.statementId ?? undefined, + dateFrom: + query.dateFrom == null ? defaultDateFrom : moment(query.dateFrom), + dateTo: query.dateTo == null ? defaultDateTo : moment(query.dateTo), + statusList: query.statusList as StatementStatusType[] | undefined + }, + controls: { + setCardId: (cardId: number) => setQuery({ cardId }, "replaceIn"), + setStatementId: (statementId: number) => + setQuery({ statementId }, "replaceIn"), + setDateFrom: (date?: Moment) => + setQuery({ dateFrom: date?.toDate() }, "replaceIn"), + setDateTo: (date?: Moment) => + setQuery({ dateTo: date?.toDate() }, "replaceIn"), + setStatusList: (statusList?: StatementStatusType[]) => + setQuery({ statusList }, "replaceIn") + } + }), + [query, setQuery] + ); +} +``` + +## 👃 코드 냄새 맡아보기 + +### 결합도 + +이 Hook은 "이 페이지에 필요한 모든 쿼리 매개변수를 관리하는 것"이라는 광범위한 책임을 가지고 있어요. 이로 인해 페이지 내의 컴포넌트나 다른 훅들이 이 훅에 의존하게 될 수 있으며, 코드 수정을 할 때 영향 범위가 급격히 확장될 수 있어요. + +시간이 지나며 이 Hook은 유지 관리가 점점 어려워지고, 수정하기 힘든 코드로 발전할 수 있어요. + +::: info + +이 Hook은 [가독성](./use-page-state-readability.md) 관점으로도 볼 수 있어요. + +::: + +## ✏️ 개선해보기 + +다음 코드와 같이 각각의 쿼리 파라미터별로 별도의 Hook을 작성할 수 있어요. + +```typescript +import { useQueryParam } from "use-query-params"; + +export function useCardIdQueryParam() { + const [cardId, _setCardId] = useQueryParam("cardId", NumberParam); + + const setCardId = useCallback((cardId: number) => { + _setCardId({ cardId }, "replaceIn"); + }, []); + + return [cardId ?? undefined, setCardId] as const; +} +``` + +Hook이 담당하는 책임을 분리했기 때문에, 수정에 따른 영향이 갈 범위를 좁힐 수 있어요. +그래서 Hook을 수정했을 때 예상하지 못한 영향이 생기는 것을 막을 수 있어요. diff --git a/ch/code/examples/use-page-state-readability.md b/ch/code/examples/use-page-state-readability.md new file mode 100644 index 00000000..fec05985 --- /dev/null +++ b/ch/code/examples/use-page-state-readability.md @@ -0,0 +1,101 @@ +# 로직 종류에 따라 합쳐진 함수 쪼개기 + +
+ +
+ +쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 만들지 마세요. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 돼요. + +## 📝 코드 예시 + +다음 `usePageState()` Hook은 페이지 전체의 URL 쿼리 파라미터를 한 번에 관리해요. + +```typescript +import moment, { Moment } from "moment"; +import { useMemo } from "react"; +import { + ArrayParam, + DateParam, + NumberParam, + useQueryParams +} from "use-query-params"; + +const defaultDateFrom = moment().subtract(3, "month"); +const defaultDateTo = moment(); + +export function usePageState() { + const [query, setQuery] = useQueryParams({ + cardId: NumberParam, + statementId: NumberParam, + dateFrom: DateParam, + dateTo: DateParam, + statusList: ArrayParam + }); + + return useMemo( + () => ({ + values: { + cardId: query.cardId ?? undefined, + statementId: query.statementId ?? undefined, + dateFrom: + query.dateFrom == null ? defaultDateFrom : moment(query.dateFrom), + dateTo: query.dateTo == null ? defaultDateTo : moment(query.dateTo), + statusList: query.statusList as StatementStatusType[] | undefined + }, + controls: { + setCardId: (cardId: number) => setQuery({ cardId }, "replaceIn"), + setStatementId: (statementId: number) => + setQuery({ statementId }, "replaceIn"), + setDateFrom: (date?: Moment) => + setQuery({ dateFrom: date?.toDate() }, "replaceIn"), + setDateTo: (date?: Moment) => + setQuery({ dateTo: date?.toDate() }, "replaceIn"), + setStatusList: (statusList?: StatementStatusType[]) => + setQuery({ statusList }, "replaceIn") + } + }), + [query, setQuery] + ); +} +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +이 Hook이 가지고 있는 책임이 "페이지가 필요한 모든 쿼리 파라미터를 관리하는 것" 임을 고려했을 때, 이 Hook이 담당할 책임이 무제한적으로 늘어날 가능성이 있어요. 새로운 쿼리 파라미터가 추가되면, 무의식적으로 이 Hook이 관리하게 되죠. + +점점 Hook이 담당하고 있는 영역이 넓어지면서, 구현이 길어지고, 어떤 역할을 하는 Hook인지 파악하기 힘들어져요. + +### 성능 + +이 Hook을 쓰는 컴포넌트는, 이 Hook이 관리하는 어떤 쿼리 파라미터가 수정되더라도 리렌더링이 발생해요. 예를 들어서, 한 컴포넌트에서 `cardId`만 참고해도, `dateFrom`이나 `dateTo`가 변경되면 리렌더링되는 거죠. + +좋은 성능을 위해서는 특정한 상태 값이 업데이트되었을 때 최소한의 부분이 리렌더링되도록 설계해야 해요. + +::: info + +이 Hook은 [결합도](./use-page-state-coupling.md) 관점으로도 볼 수 있어요. + +::: + +## ✏️ 개선해보기 + +다음 코드와 같이 각각의 쿼리 파라미터별로 별도의 Hook을 작성할 수 있어요. + +```typescript +import { useQueryParam } from "use-query-params"; + +export function useCardIdQueryParam() { + const [cardId, _setCardId] = useQueryParam("cardId", NumberParam); + + const setCardId = useCallback((cardId: number) => { + _setCardId({ cardId }, "replaceIn"); + }, []); + + return [cardId ?? undefined, setCardId] as const; +} +``` + +Hook이 담당하는 책임을 분리했기 때문에, 기존 `usePageState()` Hook보다 명확한 이름을 가져요. +또한 Hook을 수정했을 때 영향이 갈 범위를 좁혀서, 예상하지 못한 변경이 생기는 것을 막을 수 있어요. diff --git a/ch/code/examples/use-user.md b/ch/code/examples/use-user.md new file mode 100644 index 00000000..e42e29bb --- /dev/null +++ b/ch/code/examples/use-user.md @@ -0,0 +1,180 @@ +# 같은 종류의 함수는 반환 타입 통일하기 + +
+ +
+ +API 호출과 관련된 Hook들처럼 같은 종류의 함수나 Hook이 서로 다른 반환 타입을 가지면 코드의 일관성이 떨어져서, 같이 일하는 동료들이 코드를 읽는 데에 헷갈릴 수 있어요. + +## 📝 코드 예시 1: useUser + +다음 `useUser` 와 `useServerTime` Hook은 모두 API 호출과 관련된 Hook이에요. + +그렇지만 `useUser`는 `@tanstack/react-query`의 `Query` 객체를 반환하고, `useServerTime`은 서버 시간을 가져와서 데이터만 반환해요. + +```typescript 9,18 +import { useQuery } from '@tanstack/react-query'; + +function useUser() { + const query = useQuery({ + queryKey: ['user'], + queryFn: fetchUser, + }); + + return query; +} + +function useServerTime() { + const query = useQuery({ + queryKey: ['serverTime'], + queryFn: fetchServerTime, + }); + + return query.data; +} +``` + +### 👃 코드 냄새 맡아보기 + +#### 예측 가능성 + +서버 API를 호출하는 Hook의 반환 타입이 서로 다르다면, 동료들은 이런 Hook을 쓸 때마다 반환 타입이 무엇인지 확인해야 해요. `Query` 객체를 반환한다면 `data`를 꺼내야 하고, 데이터만 반환한다면 그대로 값을 사용할 수 있죠. + +같은 종류의 동작을 하는 코드가 일관적인 규칙에 따르고 있지 않으면 코드를 읽고 쓰는 데 헷갈려요. + +### ✏️ 개선해보기 + +다음 코드처럼 서버 API를 호출하는 Hook은 일관적으로 `Query` 객체를 반환하게 하면, 팀원들이 코드에 대한 예측 가능성을 높일 수 있어요. + +```typescript 9,18 +import { useQuery } from '@tanstack/react-query'; + +function useUser() { + const query = useQuery({ + queryKey: ['user'], + queryFn: fetchUser, + }); + + return query; +} + +function useServerTime() { + const query = useQuery({ + queryKey: ['serverTime'], + queryFn: fetchServerTime, + }); + + return query; +} +``` + +## 📝 코드 예시 2: checkIsValid + +다음 `checkIsNameValid` 와 `checkIsAgeValid`는 모두 이름과 나이가 올바른지 검증하는 함수예요. + +```typescript +/** 사용자 이름은 20자 미만이어야 해요. */ +function checkIsNameValid(name: string) { + const isValid = name.length > 0 && name.length < 20; + + return isValid; +} + +/** 사용자 나이는 18세 이상 99세 이하의 자연수여야 해요. */ +function checkIsAgeValid(age: number) { + if (!Number.isInteger(age)) { + return { + ok: false, + reason: "나이는 정수여야 해요." + }; + } + + if (age < 18) { + return { + ok: false, + reason: "나이는 18세 이상이어야 해요." + }; + } + + if (age > 99) { + return { + ok: false, + reason: "나이는 99세 이하이어야 해요." + }; + } + + return { ok: true }; +} +``` + +### 👃 코드 냄새 맡아보기 + +#### 예측 가능성 + +유효성 검사 함수의 반환 값이 다르다면, 동료들은 함수를 쓸 때마다 반환 타입을 확인해야 해서 혼란이 생겨요. + +특히 [엄격한 불리언 검증](https://typescript-eslint.io/rules/strict-boolean-expressions/)과 같은 기능을 사용하지 않는 경우, 코드의 오류가 생기는 원인이 될 수 있어요. + +```typescript +// 이 코드는 이름이 규칙에 맞는지 올바르게 검증해요 +if (checkIsNameValid(name)) { + // ... +} + +// 이 함수는 항상 객체 { ok, ... } 를 반환하기 때문에, +// `if` 문 안에 있는 코드가 항상 실행돼요 +if (checkIsAgeValid(age)) { + // ... +} +``` + +### ✏️ 개선해보기 + +다음 코드처럼 유효성 검사 함수가 일관적으로 `{ ok, ... }` 타입의 객체를 반환하게 할 수 있어요. + +```typescript +/** 사용자 이름은 20자 미만이어야 해요. */ +function checkIsNameValid(name: string) { + if (name.length === 0) { + return { + ok: false, + reason: "이름은 빈 값일 수 없어요." + }; + } + + if (name.length >= 20) { + return { + ok: false, + reason: '이름은 20자 이상 입력할 수 없어요.', + }; + } + + return { ok: true }; +} + +/** 사용자 나이는 18세 이상 99세 이하의 자연수여야 해요. */ +function checkIsAgeValid(age: number) { + if (!Number.isInteger(age)) { + return { + ok: false, + reason: "나이는 정수여야 해요." + }; + } + + if (age < 18) { + return { + ok: false, + reason: "나이는 18세 이상이어야 해요." + }; + } + + if (age > 99) { + return { + ok: false, + reason: "나이는 99세 이하이어야 해요." + }; + } + + return { ok: true }; +} +``` diff --git a/ch/code/examples/user-policy.md b/ch/code/examples/user-policy.md new file mode 100644 index 00000000..578535e9 --- /dev/null +++ b/ch/code/examples/user-policy.md @@ -0,0 +1,108 @@ +# 시점 이동 줄이기 + +
+ +
+ +코드를 읽을 때 코드의 위아래를 왔다갔다 하면서 읽거나, 여러 파일이나 함수, 변수를 넘나들면서 읽는 것을 **시점 이동**이라고 해요. +시점이 여러 번 이동할수록 코드를 파악하는 데에 시간이 더 걸리고, 맥락을 파악하는 데에 어려움이 있을 수 있어요. + +코드를 위에서 아래로, 하나의 함수나 파일에서 읽을 수 있도록 코드를 작성하면, 읽는 사람이 동작을 빠르게 파악할 수 있게 돼요. + +## 📝 코드 예시 + +다음 코드에서는 사용자의 권한에 따라서 버튼을 다르게 보여줘요. + +- 사용자의 권한이 관리자(Admin)라면, `Invite`와 `View` 버튼을 보여줘요. +- 사용자의 권한이 보기 전용(Viewer)라면, `Invite` 버튼은 비활성화하고, `View` 버튼을 보여줘요. + +```tsx +function Page() { + const user = useUser(); + const policy = getPolicyByRole(user.role); + + return ( +
+ + +
+ ); +} + +function getPolicyByRole(role) { + const policy = POLICY_SET[role]; + + return { + canInvite: policy.includes("invite"), + canView: policy.includes("view") + }; +} + +const POLICY_SET = { + admin: ["invite", "view"], + viewer: ["view"] +}; +``` + +## 👃 코드 냄새 맡아보기 + +### 가독성 + +이 코드에서 `Invite` 버튼이 비활성화된 이유를 이해하려고 한다면, `policy.canInvite` → `getPolicyByRole(user.role)` → `POLICY_SET` 순으로 코드를 위아래를 오가며 읽어야 해요. +이 과정에서 3번의 시점 이동이 발생해서, 코드를 읽는 사람이 맥락을 유지해 가며 읽기 어려워졌어요. + +`POLICY_SET` 같은 추상화를 사용해서 권한에 따라 버튼 상태를 관리하는 것은 권한 체계가 복잡한 경우에는 유용할 수 있지만, 지금처럼 간단할 때는 오히려 읽는 사람이 코드를 이해하기 어렵게 만들어요. + +## ✏️ 개선해보기 + +### A. 조건을 펼쳐서 그대로 드러내기 + +권한에 따른 조건을 요구사항 그대로 코드에 드러내는 방법이에요. 이렇게 하면 `Invite` 버튼이 비활성화되는 때를 코드에서 바로 확인할 수 있어요. +코드를 위에서 아래로만 읽으면 한눈에 권한을 다루는 로직을 파악할 수 있어요. + +```tsx +function Page() { + const user = useUser(); + + switch (user.role) { + case "admin": + return ( +
+ + +
+ ); + case "viewer": + return ( +
+ + +
+ ); + default: + return null; + } +} +``` + +### B. 조건을 한눈에 볼 수 있는 객체로 만들기 + +권한을 다루는 로직을 컴포넌트 안에서 객체로 관리해서, 여러 차례의 시점 이동 없이 한눈에 조건을 파악할 수 있게 수정할 수 있어요. +`canInvite`와 `canView`의 조건을 `Page` 컴포넌트만 보면 확인할 수 있어요. + +```tsx +function Page() { + const user = useUser(); + const policy = { + admin: { canInvite: true, canView: true }, + viewer: { canInvite: false, canView: true }, + }[user.role]; + + return ( +
+ + +
+ ); +} +``` diff --git a/ch/code/index.md b/ch/code/index.md new file mode 100644 index 00000000..bebffdf1 --- /dev/null +++ b/ch/code/index.md @@ -0,0 +1,81 @@ +--- +comments: false +--- + +# 변경하기 쉬운 코드 + +좋은 프론트엔드 코드는 **변경하기 쉬운** 코드예요. +새로운 요구사항을 구현하고자 할 때, 기존 코드를 수정하고 배포하기 수월한 코드가 좋은 코드죠. +코드가 변경하기 쉬운지는 4가지 기준으로 판단할 수 있어요. + +## 1. 가독성 + +**가독성**(Readability)은 코드가 읽기 쉬운 정도를 말해요. +코드가 변경하기 쉬우려면 먼저 코드가 어떤 동작을 하는지 이해할 수 있어야 해요. + +읽기 좋은 코드는 읽는 사람이 한 번에 머릿속에서 고려하는 맥락이 적고, 위에서 아래로 자연스럽게 이어져요. + +### 가독성을 높이는 전략 + +- **맥락 줄이기** + - [같이 실행되지 않는 코드 분리하기](./examples/submit-button.md) + - [구현 상세 추상화하기](./examples/login-start-page.md) + - [로직 종류에 따라 합쳐진 함수 쪼개기](./examples/use-page-state-readability.md) +- **이름 붙이기** + - [복잡한 조건에 이름 붙이기](./examples/condition-name.md) + - [매직 넘버에 이름 붙이기](./examples/magic-number-readability.md) +- **위에서 아래로 읽히게 하기** + - [시점 이동 줄이기](./examples/user-policy.md) + - [삼항 연산자 단순하게 하기](./examples/ternary-operator.md) + +## 2. 예측 가능성 + +**예측 가능성**(Predictability)이란, 함께 협업하는 동료들이 함수나 컴포넌트의 동작을 얼마나 예측할 수 있는지를 말해요. +예측 가능성이 높은 코드는 일관적인 규칙을 따르고, 함수나 컴포넌트의 이름과 파라미터, 반환 값만 보고도 어떤 동작을 하는지 알 수 있어요. + +### 예측 가능성을 높이는 전략 + +- [이름 겹치지 않게 관리하기](./examples/http.md) +- [같은 종류의 함수는 반환 타입 통일하기](./examples/use-user.md) +- [숨은 로직 드러내기](./examples/hidden-logic.md) + +## 3. 응집도 + +**응집도**(Cohesion)란, 수정되어야 할 코드가 항상 같이 수정되는지를 말해요. +응집도가 높은 코드는 코드의 한 부분을 수정해도 의도치 않게 다른 부분에서 오류가 발생하지 않아요. +함께 수정되어야 할 부분이 반드시 함께 수정되도록 구조적으로 뒷받침되기 때문이죠. + +::: info 가독성과 응집도는 서로 상충할 수 있어요 + +일반적으로 응집도를 높이기 위해서는 변수나 함수를 추상화하는 등 가독성을 떨어뜨리는 결정을 해야 해요. +함께 수정되지 않으면 오류가 발생할 수 있는 경우에는, 응집도를 우선해서 코드를 공통화, 추상화하세요. +위험성이 높지 않은 경우에는, 가독성을 우선하여 코드 중복을 허용하세요. + +::: + +### 응집도를 높이는 전략 + +- [함께 수정되는 파일을 같은 디렉토리에 두기](./examples/code-directory.md) +- [매직 넘버 없애기](./examples/magic-number-cohesion.md) +- [폼의 응집도 생각하기](./examples/form-fields.md) + +## 4. 결합도 + +**결합도**(Coupling)란, 코드를 수정했을 때의 영향범위를 말해요. +코드를 수정했을 때 영향범위가 적어서, 변경에 따른 범위를 예측할 수 있는 코드가 수정하기 쉬운 코드예요. + +### 결합도를 낮추는 전략 + +- [책임을 하나씩 관리하기](./examples/use-page-state-coupling.md) +- [중복 코드 허용하기](./examples/use-bottom-sheet.md) +- [Props Drilling 지우기](./examples/item-edit-modal.md) + +## 코드 품질 여러 각도로 보기 + +아쉽게도 이 4가지 기준을 모두 한꺼번에 충족하기는 어려워요. + +예를 들어서, 함수나 변수가 항상 같이 수정되기 위해서 공통화 및 추상화하면, 응집도가 높아지죠. 그렇지만 코드가 한 차례 추상화되기 때문에 가독성이 떨어져요. + +중복 코드를 허용하면, 코드의 영향범위를 줄일 수 있어서, 결합도를 낮출 수 있어요. 그렇지만 한쪽을 수정했을 때 다른 한쪽을 실수로 수정하지 못할 수 있어서, 응집도가 떨어지죠. + +프론트엔드 개발자는 현재 직면한 상황을 바탕으로, 깊이 있게 고민하면서, 장기적으로 코드가 수정하기 쉽게 하기 위해서 어떤 가치를 우선해야 하는지 고민해야 해요. diff --git a/ch/code/start.md b/ch/code/start.md new file mode 100644 index 00000000..534fe1f6 --- /dev/null +++ b/ch/code/start.md @@ -0,0 +1,34 @@ +--- +comments: false +--- + +# 시작하기 + +`Frontend Fundamentals`(FF)는 좋은 프론트엔드 코드의 기준을 제공해요. +프론트엔드 개발자로서 코드 품질을 높이고자 할 때 방향을 찾는 나침반처럼 활용해 보세요. + +좋은 코드에 대한 [4개 원칙](./index.md)과 함께, 구체적인 예시 및 해결 방안을 제시해요. + +## 이럴 때 활용해 보세요 + +- 🦨 코드에 대해서 고민되는데 **논리적으로 설명하기 어려운 개발자** +- 👀 **나쁜 코드를 빠르게 감지**하고 개선하는 방법을 공부하고 싶은 개발자 +- 🤓 코드 리뷰 등에서 누가 전달해준 링크를 타고 들어와 "내 코드가 이랬구나"를 **객관적 시각으로 인지**하게 될 개발자 +- 👥 **팀과 함께** 공통의 코딩 스타일과 코드 품질의 기준을 세워보고 싶은 개발자 + +## 저작자 + +- [milooy](https://github.com/milooy) +- [donghyeon](https://github.com/kimbangg) +- [chkim116](https://github.com/chkim116) +- [inseong.you](https://github.com/inseong.you) +- [raon0211](https://github.com/raon0211) +- [bigsaigon333](https://github.com/bigsaigon333) +- [jho2301](https://github.com/jho2301) +- [KimChunsick](https://github.com/KimChunsick) +- [jennybehan](https://github.com/jennybehan) + +## 문서 기여자 + +- [andy0414](https://github.com/andy0414) +- [pumpkiinbell](https://github.com/pumpkiinbell) diff --git a/ch/index.md b/ch/index.md new file mode 100644 index 00000000..8c8e88c3 --- /dev/null +++ b/ch/index.md @@ -0,0 +1,28 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Frontend Fundamentals" + tagline: "변경하기 쉬운 프론트엔드 코드를 위한 지침서" + image: + src: /images/ff-symbol-gradient.png + alt: Frontend Fundamentals symbol + actions: + - text: 좋은 코드의 기준 알아보기 + link: /code/ + - theme: alt + text: 소통하기 + link: /code/community + +features: + - icon: 🤓 + title: 코드를 보는 눈을 키우고 싶다면 + details: 변경하기 쉬운 코드인지 판단하기 위한 원칙을 살펴보세요. + - icon: 🤝 + title: 코드 리뷰를 잘하고 싶다면 + details: 다양한 코드 개선 사례를 능동적으로 탐색해 보세요. + - icon: 📝 + title: 내 코드가 고민된다면 + details: 깃허브 디스커션에서 다른 개발자들과 소통해 보세요. +--- From b96b3a61f4d7e433d63af8c547f18d1a7faba557 Mon Sep 17 00:00:00 2001 From: owonie Date: Sun, 19 Jan 2025 04:42:02 +0900 Subject: [PATCH 02/16] docs: translate into simplified chinese --- .vitepress/ch.mts | 144 +++++++++++++++++++++--------------------- .vitepress/config.mts | 2 + ch/code/start.md | 21 +++--- 3 files changed, 84 insertions(+), 83 deletions(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 4fb7928d..8f775a21 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -1,9 +1,9 @@ import { type DefaultTheme, defineConfig } from "vitepress"; -export const ko = defineConfig({ - lang: "ko", +export const ch = defineConfig({ + lang: "ch", title: "Frontend Fundamentals", - description: "변경하기 쉬운 프론트엔드 코드를 위한 지침서", + description: "易于修改的前端代码指南", lastUpdated: true, themeConfig: { logo: "/images/ff-symbol.svg", @@ -11,18 +11,18 @@ export const ko = defineConfig({ editLink: { pattern: "https://github.com/toss/frontend-fundamentals/edit/main/:path", - text: "GitHub에서 수정하기" + text: "在GitHub编辑此页" }, outline: { - label: "페이지 내용" + label: "页面内容" }, docFooter: { - prev: "이전 페이지", - next: "다음 페이지" + prev: "上一页", + next: "下一页" }, lastUpdated: { - text: "마지막 업데이트" + text: "最后更新" }, sidebar: sidebar() @@ -30,31 +30,31 @@ export const ko = defineConfig({ }); function nav(): DefaultTheme.NavItem[] { - return [{ text: "홈", link: "/" }]; + return [{ text: "首页", link: "/ch" }]; } function sidebar(): DefaultTheme.Sidebar { return [ { - text: "좋은 코드의 기준", + text: "好代码的标准", items: [ { - text: "시작하기", - link: "/code/start" + text: "开始使用", + link: "/ch/code/start" }, { - text: "변경하기 쉬운 코드", - link: "/code/" + text: "易于修改的代码", + link: "/ch/code/" }, { - text: "커뮤니티", + text: "社区", items: [ { - text: "소개", - link: "/code/community" + text: "介绍", + link: "/ch/code/community" }, { - text: "⭐ 좋은 논의 모아보기", + text: "⭐ 专题讨论", link: "https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22" }, { @@ -62,7 +62,7 @@ function sidebar(): DefaultTheme.Sidebar { link: "https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b?discussions_q=is%3Aopen+category%3A%22A+vs+B%22+sort%3Adate_created" }, { - text: "자유로운 이야기", + text: "公开论坛", link: "https://github.com/toss/frontend-fundamentals/discussions/categories/open-forum?discussions_q=is%3Aopen+sort%3Adate_created+category%3A%22Open+Forum%22" } ], @@ -71,53 +71,53 @@ function sidebar(): DefaultTheme.Sidebar { ] }, { - text: "좋은 코드를 만드는 전략", + text: "编写好代码的策略", items: [ { - text: "1. 가독성", + text: "1. 可读性", items: [ { - text: "맥락 줄이기", + text: "减少语境", items: [ { - text: "A. 같이 실행되지 않는 코드 분리하기", - link: "/code/examples/submit-button" + text: "A. 拆分不同语境下的代码", + link: "/ch/code/examples/submit-button" }, { - text: "B. 구현 상세 추상화하기", - link: "/code/examples/login-start-page" + text: "B. 抽象实现细节", + link: "/ch/code/examples/login-start-page" }, { - text: "C. 로직 종류에 따라 합쳐진 함수 쪼개기", - link: "/code/examples/use-page-state-readability" + text: "C. 根据逻辑类型拆分合并的函数", + link: "/ch/code/examples/use-page-state-readability" } ], collapsed: true }, { - text: "이름 붙이기", + text: "命名", items: [ { - text: "A. 복잡한 조건에 이름 붙이기", - link: "/code/examples/condition-name" + text: "A. 给复杂条件命名", + link: "/ch/code/examples/condition-name" }, { - text: "B. 매직 넘버에 이름 붙이기", - link: "/code/examples/magic-number-readability" + text: "B. 给魔数命名", + link: "/ch/code/examples/magic-number-readability" } ], collapsed: true }, { - text: "위에서 아래로 읽히게 하기", + text: "使其从上到下顺利阅读", items: [ { - text: "A. 시점 이동 줄이기", - link: "/code/examples/user-policy" + text: "A. 减少视点转换", + link: "/ch/code/examples/user-policy" }, { - text: "B. 삼항 연산자 단순하게 하기", - link: "/code/examples/ternary-operator" + text: "B. 简化三元运算符", + link: "/ch/code/examples/ternary-operator" } ], collapsed: true @@ -126,53 +126,53 @@ function sidebar(): DefaultTheme.Sidebar { }, { - text: "2. 예측 가능성", + text: "2. 可预测性", items: [ { - text: "A. 이름 겹치지 않게 관리하기", - link: "/code/examples/http" + text: "A. 避免命名重复", + link: "/ch/code/examples/http" }, { - text: "B. 같은 종류의 함수는 반환 타입 통일하기", - link: "/code/examples/use-user" + text: "B. 相同类型的函数统一返回类型", + link: "/ch/code/examples/use-user" }, { - text: "C. 숨은 로직 드러내기", - link: "/code/examples/hidden-logic" + text: "C. 揭示隐藏的逻辑", + link: "/ch/code/examples/hidden-logic" } ] }, { - text: "3. 응집도", + text: "3. 内聚性", items: [ { - text: "A. 함께 수정되는 파일을 같은 디렉토리에 두기", - link: "/code/examples/code-directory" + text: "A. 需同时修改的文件位于同一目录下", + link: "/ch/code/examples/code-directory" }, { - text: "B. 매직 넘버 없애기", - link: "/code/examples/magic-number-cohesion" + text: "B. 消除魔数", + link: "/ch/code/examples/magic-number-cohesion" }, { - text: "C. 폼의 응집도 생각하기", - link: "/code/examples/form-fields" + text: "C. 考虑表单的内聚性", + link: "/ch/code/examples/form-fields" } ] }, { - text: "4. 결합도", + text: "4. 耦合性", items: [ { - text: "A. 책임을 하나씩 관리하기", - link: "/code/examples/use-page-state-coupling" + text: "A. 单独管理责任", + link: "/ch/code/examples/use-page-state-coupling" }, { - text: "B. 중복 코드 허용하기", - link: "/code/examples/use-bottom-sheet" + text: "B. 允许重复代码", + link: "/ch/code/examples/use-bottom-sheet" }, { - text: "C. Props Drilling 지우기", - link: "/code/examples/item-edit-modal" + text: "C. 消除 Props Drilling", + link: "/ch/code/examples/item-edit-modal" } ] } @@ -185,23 +185,23 @@ export const search: DefaultTheme.LocalSearchOptions["locales"] = { root: { translations: { button: { - buttonText: "검색", - buttonAriaLabel: "검색" + buttonText: "搜索", + buttonAriaLabel: "搜索" }, modal: { - backButtonTitle: "뒤로가기", - displayDetails: "더보기", + backButtonTitle: "返回", + displayDetails: "更多", footer: { - closeKeyAriaLabel: "닫기", - closeText: "닫기", - navigateDownKeyAriaLabel: "아래로", - navigateText: "이동", - navigateUpKeyAriaLabel: "위로", - selectKeyAriaLabel: "선택", - selectText: "선택" + closeKeyAriaLabel: "关闭", + closeText: "关闭", + navigateDownKeyAriaLabel: "向下", + navigateText: "移动", + navigateUpKeyAriaLabel: "向上", + selectKeyAriaLabel: "选择", + selectText: "选择" }, - noResultsText: "검색 결과를 찾지 못했어요.", - resetButtonTitle: "모두 지우기" + noResultsText: "没有搜索结果。", + resetButtonTitle: "全部清除" } } } diff --git a/.vitepress/config.mts b/.vitepress/config.mts index 9bf528aa..471f3ac0 100644 --- a/.vitepress/config.mts +++ b/.vitepress/config.mts @@ -6,6 +6,7 @@ import { shared } from "./shared.mts"; import { en } from "./en.mts"; import { ko } from "./ko.mts"; import { ja } from "./ja.mts"; +import { ch } from "./ch.mjs"; const require = createRequire(import.meta.url); @@ -14,6 +15,7 @@ export default defineConfig({ locales: { en: { label: "English", ...en }, ja: { label: "日本語", ...ja }, + ch: { label: "简体中文", ...ch }, root: { label: "한국어", ...ko } }, vite: { diff --git a/ch/code/start.md b/ch/code/start.md index 534fe1f6..f564d110 100644 --- a/ch/code/start.md +++ b/ch/code/start.md @@ -2,21 +2,20 @@ comments: false --- -# 시작하기 +# 开始使用 -`Frontend Fundamentals`(FF)는 좋은 프론트엔드 코드의 기준을 제공해요. -프론트엔드 개발자로서 코드 품질을 높이고자 할 때 방향을 찾는 나침반처럼 활용해 보세요. +`Frontend Fundamentals`(FF)为前端代码规范提供标准。作为前端开发者,当你想提升代码质量时,可以将它做为指南针,帮助你找到正确的方向。 -좋은 코드에 대한 [4개 원칙](./index.md)과 함께, 구체적인 예시 및 해결 방안을 제시해요. +它介绍了好代码的[四大原则](./index.md),以及具体的实例与解决方案。 -## 이럴 때 활용해 보세요 +## 何时使用 -- 🦨 코드에 대해서 고민되는데 **논리적으로 설명하기 어려운 개발자** -- 👀 **나쁜 코드를 빠르게 감지**하고 개선하는 방법을 공부하고 싶은 개발자 -- 🤓 코드 리뷰 등에서 누가 전달해준 링크를 타고 들어와 "내 코드가 이랬구나"를 **객관적 시각으로 인지**하게 될 개발자 -- 👥 **팀과 함께** 공통의 코딩 스타일과 코드 품질의 기준을 세워보고 싶은 개발자 +- 🦨 关心代码但**难以从逻辑上解释的开发者**。 +- 👀 想学习如何**快速检测并改善坏代码**的开发者。 +- 🤓 在代码审查等环节,通过别人分享的链接**将客观地认识**自己是如何编写代码的开发者。 +- 👥 **希望与团队**一起制定共同代码风格和质量标准的开发者。 -## 저작자 +## 作者 - [milooy](https://github.com/milooy) - [donghyeon](https://github.com/kimbangg) @@ -28,7 +27,7 @@ comments: false - [KimChunsick](https://github.com/KimChunsick) - [jennybehan](https://github.com/jennybehan) -## 문서 기여자 +## 文档贡献者 - [andy0414](https://github.com/andy0414) - [pumpkiinbell](https://github.com/pumpkiinbell) From eb8b152e39c2033e77a4da8ac5b7648a921c203f Mon Sep 17 00:00:00 2001 From: owonie Date: Wed, 22 Jan 2025 01:26:55 +0900 Subject: [PATCH 03/16] docs: translate into simplified chinese --- .vitepress/ch.mts | 2 +- ch/index.md | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 8f775a21..92227f6e 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -133,7 +133,7 @@ function sidebar(): DefaultTheme.Sidebar { link: "/ch/code/examples/http" }, { - text: "B. 相同类型的函数统一返回类型", + text: "B. 统一同类函数的返回类型", link: "/ch/code/examples/use-user" }, { diff --git a/ch/index.md b/ch/index.md index 8c8e88c3..b20070a9 100644 --- a/ch/index.md +++ b/ch/index.md @@ -4,25 +4,25 @@ layout: home hero: name: "Frontend Fundamentals" - tagline: "변경하기 쉬운 프론트엔드 코드를 위한 지침서" + tagline: "易于修改的前端代码指南" image: src: /images/ff-symbol-gradient.png alt: Frontend Fundamentals symbol actions: - - text: 좋은 코드의 기준 알아보기 - link: /code/ + - text: 了解好代码的标准 + link: /ch/code/ - theme: alt - text: 소통하기 - link: /code/community + text: 社区 + link: /ch/code/community features: - icon: 🤓 - title: 코드를 보는 눈을 키우고 싶다면 - details: 변경하기 쉬운 코드인지 판단하기 위한 원칙을 살펴보세요. + title: 提高你的代码阅读能力 + details: 查看判断代码是否易于修改的原则。 - icon: 🤝 - title: 코드 리뷰를 잘하고 싶다면 - details: 다양한 코드 개선 사례를 능동적으로 탐색해 보세요. + title: 提高你的代码审查能力 + details: 主动探索各种代码改善的实例。 - icon: 📝 - title: 내 코드가 고민된다면 - details: 깃허브 디스커션에서 다른 개발자들과 소통해 보세요. + title: 如果对自己的代码感到困惑 + details: 在 GitHub 社区与其他开发者进行交流。 --- From b206a65eeb143a7028269df43b01e806e29a44b0 Mon Sep 17 00:00:00 2001 From: owonie Date: Sat, 25 Jan 2025 00:55:14 +0900 Subject: [PATCH 04/16] docs: translate into simplified chinese --- .vitepress/ch.mts | 4 +- ch/code/community.md | 32 +++++++------- ch/code/index.md | 100 +++++++++++++++++++++---------------------- ch/code/start.md | 2 +- ch/index.md | 2 +- 5 files changed, 70 insertions(+), 70 deletions(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 92227f6e..2af3e714 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -98,11 +98,11 @@ function sidebar(): DefaultTheme.Sidebar { text: "命名", items: [ { - text: "A. 给复杂条件命名", + text: "A. 为复杂条件命名", link: "/ch/code/examples/condition-name" }, { - text: "B. 给魔数命名", + text: "B. 为魔数命名", link: "/ch/code/examples/magic-number-readability" } ], diff --git a/ch/code/community.md b/ch/code/community.md index e8d0938d..2299ee81 100644 --- a/ch/code/community.md +++ b/ch/code/community.md @@ -2,32 +2,32 @@ comments: false --- -# 커뮤니티 +# 社区 -`Frontend Fundamentals`(FF)는 커뮤니티와 함께 좋은 코드의 기준을 만들어 가고 있어요. +`Frontend Fundamentals`(FF)与社区一起制定好代码的标准。 -지금은 토스 프론트엔드 챕터가 운영하고 있어요. +目前由 Toss 前端分部维护。 -## 좋은 논의 모아보기 +## 专题讨论 -커뮤니티에서 있었던 좋은 논의를 살펴보세요. Frontend Fundamentals 문서에 나오는 내용을 넘어, 좋은 코드에 대한 생각을 넓힐 수 있어요. +查看社区中的精彩讨论。超出《Frontend Fundamentals》 文档中的内容,拓宽你对好代码的思考。 -- [좋은 논의 모아보기](https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22) +- [专题讨论](https://github.com/toss/frontend-fundamentals/discussions?discussions_q=is%3Aopen+label%3A%22%EC%84%B1%EC%A7%80+%E2%9B%B2%22) -## 고민되는 코드에 대해 논의하기 +## 探讨代码疑虑 -고민되는 코드가 있다면 깃허브 디스커션에 글을 올려 보세요. -내 코드에 대해서 커뮤니티에서 다각도로 리뷰를 받을 수 있고, 좋은 코드의 기준에 대해 커뮤니티와 함께 고민할 수 있어요. +如果你有令人疑惑的代码,可以将其发布到 GitHub 论坛上。 +可以在社区中多角度审查你的代码,并与社区一起探讨好代码的标准。 -많은 공감을 받은 사례는 직접 Frontend Fundamentals 문서에 올릴 수 있어요. 기여 방법은 추후 공개될 예정이에요. +受到广泛共鸣的案例可直接上传到 Frontend Fundamentals 文档中。贡献方法将会稍后公布。 -- [깃허브 디스커션에 글 올리기](https://github.com/toss/frontend-fundamentals/discussions) +- [在 GitHub 论坛上发帖](https://github.com/toss/frontend-fundamentals/discussions) -## 좋은 코드의 기준에 의견 더하기 +## 为好代码标准添加意见 -좋은 코드의 기준에 대해 의견이 있거나, 새로운 의견을 더하고 싶다면 더 좋은 코드가 어떤 코드인지 투표하고, 의견을 남겨 보세요. -커뮤니티와 소통하며 더욱 풍부하고 깊이 있는 기준을 만들어 나가요. +如果对好代码的标准有意见,或者想提出新的观点,可以参与投票选出你认为更好的代码,并留下自己的意见。 +与社区沟通,共同构建更加丰富而深入的标准。 -이 코드가 좋을까? 저 코드가 좋을까? 에 대해서 나만의 기준을 확립하는 계기가 될 수 있어요. +这可以成为一个契机,帮助你确立判断两段代码之间哪一段更好的标准。 -- [A vs B에 올라온 코드 보기](https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b) +- [查看 A vs B 上的代码](https://github.com/toss/frontend-fundamentals/discussions/categories/a-vs-b) diff --git a/ch/code/index.md b/ch/code/index.md index bebffdf1..091833ef 100644 --- a/ch/code/index.md +++ b/ch/code/index.md @@ -2,80 +2,80 @@ comments: false --- -# 변경하기 쉬운 코드 +# 易于修改的代码 -좋은 프론트엔드 코드는 **변경하기 쉬운** 코드예요. -새로운 요구사항을 구현하고자 할 때, 기존 코드를 수정하고 배포하기 수월한 코드가 좋은 코드죠. -코드가 변경하기 쉬운지는 4가지 기준으로 판단할 수 있어요. +好的前端代码是 **易于修改的** 代码。 +在实现新需求时,能够轻松修改和部署的代码被认为好的代码。 +你可以根据四个标准判断代码是否易于修改。 -## 1. 가독성 +## 1. 可读性 -**가독성**(Readability)은 코드가 읽기 쉬운 정도를 말해요. -코드가 변경하기 쉬우려면 먼저 코드가 어떤 동작을 하는지 이해할 수 있어야 해요. +**可读性**(Readability)指的是代码易于阅读和理解的程度。 +要使代码易于修改,首先必须理解代码的作用。 -읽기 좋은 코드는 읽는 사람이 한 번에 머릿속에서 고려하는 맥락이 적고, 위에서 아래로 자연스럽게 이어져요. +易于阅读的代码要求读者考虑的上下文较少,从上到下自然流畅。 -### 가독성을 높이는 전략 +### 提高可读性的策略 -- **맥락 줄이기** - - [같이 실행되지 않는 코드 분리하기](./examples/submit-button.md) - - [구현 상세 추상화하기](./examples/login-start-page.md) - - [로직 종류에 따라 합쳐진 함수 쪼개기](./examples/use-page-state-readability.md) -- **이름 붙이기** - - [복잡한 조건에 이름 붙이기](./examples/condition-name.md) - - [매직 넘버에 이름 붙이기](./examples/magic-number-readability.md) -- **위에서 아래로 읽히게 하기** - - [시점 이동 줄이기](./examples/user-policy.md) - - [삼항 연산자 단순하게 하기](./examples/ternary-operator.md) +- **减少语境** + - [拆分不同语境下的代码](./examples/submit-button.md) + - [抽象实现细节](./examples/login-start-page.md) + - [根据逻辑类型拆分合并的函数](./examples/use-page-state-readability.md) +- **命名** + - [为复杂条件命名](./examples/condition-name.md) + - [为魔数命名](./examples/magic-number-readability.md) +- **使其从上到下顺利阅读** + - [减少视点转换](./examples/user-policy.md) + - [简化三元运算符](./examples/ternary-operator.md) -## 2. 예측 가능성 +## 2. 可预测性 -**예측 가능성**(Predictability)이란, 함께 협업하는 동료들이 함수나 컴포넌트의 동작을 얼마나 예측할 수 있는지를 말해요. -예측 가능성이 높은 코드는 일관적인 규칙을 따르고, 함수나 컴포넌트의 이름과 파라미터, 반환 값만 보고도 어떤 동작을 하는지 알 수 있어요. +**可预测性**(Predictability)指的是与团队成员协作时,同事能够预测函数或组件行为的难易程度。 +可预测性高的代码遵循一致的规则,仅通过函数或组件的名称、参数、返回值,就能知道其执行的行为。 -### 예측 가능성을 높이는 전략 +### 提高可预测性的战略 -- [이름 겹치지 않게 관리하기](./examples/http.md) -- [같은 종류의 함수는 반환 타입 통일하기](./examples/use-user.md) -- [숨은 로직 드러내기](./examples/hidden-logic.md) +- [避免命名重复](./examples/http.md) +- [统一同类函数的返回类型](./examples/use-user.md) +- [解释隐藏的逻辑](./examples/hidden-logic.md) -## 3. 응집도 +## 3. 内聚性 -**응집도**(Cohesion)란, 수정되어야 할 코드가 항상 같이 수정되는지를 말해요. -응집도가 높은 코드는 코드의 한 부분을 수정해도 의도치 않게 다른 부분에서 오류가 발생하지 않아요. -함께 수정되어야 할 부분이 반드시 함께 수정되도록 구조적으로 뒷받침되기 때문이죠. +**内聚性**(Cohesion)是指需要被修改的代码是否总是一起修改的特性。 +内聚性高的代码,即使修改了某一部分,也不会在其他部分引发障碍。 +这是因为在结构上确保了需要修改的代码一同修改。 -::: info 가독성과 응집도는 서로 상충할 수 있어요 +::: info 可读性与内聚性可能存在冲突 -일반적으로 응집도를 높이기 위해서는 변수나 함수를 추상화하는 등 가독성을 떨어뜨리는 결정을 해야 해요. -함께 수정되지 않으면 오류가 발생할 수 있는 경우에는, 응집도를 우선해서 코드를 공통화, 추상화하세요. -위험성이 높지 않은 경우에는, 가독성을 우선하여 코드 중복을 허용하세요. +一般来说,为了提高内聚性,可能需要做出一些降低可读性的决策,例如抽象化变数或函数。 +以内聚性为准,通过代码的通用化和抽象化来避免未同时修改而引发的障碍。 +风险较低时,应优先考虑可读性,允许代码重复。 ::: -### 응집도를 높이는 전략 +### 提高内聚性的策略 -- [함께 수정되는 파일을 같은 디렉토리에 두기](./examples/code-directory.md) -- [매직 넘버 없애기](./examples/magic-number-cohesion.md) -- [폼의 응집도 생각하기](./examples/form-fields.md) +- [需同时修改的文件位于同一目录下](./examples/code-directory.md) +- [消除魔数](./examples/magic-number-cohesion.md) +- [考虑表单的内聚性](./examples/form-fields.md) -## 4. 결합도 +## 4. 耦合性 -**결합도**(Coupling)란, 코드를 수정했을 때의 영향범위를 말해요. -코드를 수정했을 때 영향범위가 적어서, 변경에 따른 범위를 예측할 수 있는 코드가 수정하기 쉬운 코드예요. +**耦合性**(Coupling)是指修改代码时的影响范围。 +易于修改的代码被修改时影响范围小,因此更容易预测更改的范围。 -### 결합도를 낮추는 전략 +### 降低耦合性的策略 -- [책임을 하나씩 관리하기](./examples/use-page-state-coupling.md) -- [중복 코드 허용하기](./examples/use-bottom-sheet.md) -- [Props Drilling 지우기](./examples/item-edit-modal.md) +- [单独管理责任](./examples/use-page-state-coupling.md) +- [允许重复代码](./examples/use-bottom-sheet.md) +- [消除 Props Drilling](./examples/item-edit-modal.md) -## 코드 품질 여러 각도로 보기 +## 多角度审视代码质量 -아쉽게도 이 4가지 기준을 모두 한꺼번에 충족하기는 어려워요. +遗憾的是,这四个标准很难同时兼顾。 -예를 들어서, 함수나 변수가 항상 같이 수정되기 위해서 공통화 및 추상화하면, 응집도가 높아지죠. 그렇지만 코드가 한 차례 추상화되기 때문에 가독성이 떨어져요. +例如,通过共同化和抽象化提高代码的内聚性,可确保函数或变数总是一同修改。代码进一步抽象化后,可读性也会随之降低。 -중복 코드를 허용하면, 코드의 영향범위를 줄일 수 있어서, 결합도를 낮출 수 있어요. 그렇지만 한쪽을 수정했을 때 다른 한쪽을 실수로 수정하지 못할 수 있어서, 응집도가 떨어지죠. +允许代码重复可以减少代码的影响范围,从而降低耦合性。然而,这也可能导致在修改一处代码时,另一处未被及时修改,从而影响内聚性。 -프론트엔드 개발자는 현재 직면한 상황을 바탕으로, 깊이 있게 고민하면서, 장기적으로 코드가 수정하기 쉽게 하기 위해서 어떤 가치를 우선해야 하는지 고민해야 해요. +前端开发者需要结合当前面临的具体情况,深入思考并在不同价值之间权衡取舍,以确保代码在长期内更易于维护和修改。 diff --git a/ch/code/start.md b/ch/code/start.md index f564d110..e50ac93c 100644 --- a/ch/code/start.md +++ b/ch/code/start.md @@ -4,7 +4,7 @@ comments: false # 开始使用 -`Frontend Fundamentals`(FF)为前端代码规范提供标准。作为前端开发者,当你想提升代码质量时,可以将它做为指南针,帮助你找到正确的方向。 +`Frontend Fundamentals`(FF)为前端代码规范提供标准。作为前端开发者,当你想提高代码质量时,可以将它做为指南针,帮助你找到正确的方向。 它介绍了好代码的[四大原则](./index.md),以及具体的实例与解决方案。 diff --git a/ch/index.md b/ch/index.md index b20070a9..920efd9f 100644 --- a/ch/index.md +++ b/ch/index.md @@ -24,5 +24,5 @@ features: details: 主动探索各种代码改善的实例。 - icon: 📝 title: 如果对自己的代码感到困惑 - details: 在 GitHub 社区与其他开发者进行交流。 + details: 在 GitHub 论坛与其他开发者进行交流。 --- From 2824cc87a561715b97e0a17bfa6cb350695fa0a4 Mon Sep 17 00:00:00 2001 From: owonie Date: Sat, 25 Jan 2025 02:25:18 +0900 Subject: [PATCH 05/16] docs: translate into simplified chinese --- ch/code/examples/code-directory.md | 40 ++++++++++++------------- ch/code/examples/condition-name.md | 48 ++++++++++++++---------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/ch/code/examples/code-directory.md b/ch/code/examples/code-directory.md index 8131ccbb..a3f5d53c 100644 --- a/ch/code/examples/code-directory.md +++ b/ch/code/examples/code-directory.md @@ -1,16 +1,16 @@ -# 함께 수정되는 파일을 같은 디렉토리에 두기 +# 需同时修改的文件位于同一目录下
- +
-프로젝트에서 코드를 작성하다 보면 Hook, 컴포넌트, 유틸리티 함수 등을 여러 파일로 나누어서 관리하게 돼요. 이런 파일들을 쉽게 만들고, 찾고, 삭제할 수 있도록 올바른 디렉토리 구조를 갖추는 것이 중요해요. +在项目中编写代码时,通常会将 Hook、组件和工具函数等拆分到多个文件进行管理。为了更轻松地创建、查找和删除这些文件,设计一个合适的目录结构至关重要。 -함께 수정되는 소스 파일을 하나의 디렉토리에 배치하면 코드의 의존 관계를 명확하게 드러낼 수 있어요. 그래서 참조하면 안 되는 파일을 함부로 참조하는 것을 막고, 연관된 파일들을 한 번에 삭제할 수 있어요. +将需要一起修改的源文件放在同一目录下,可以更直观地展现代码的依赖关系。这不仅可以防止随意引用不应被引用的文件,还能一次性删除相关文件。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 프로젝트의 모든 파일을 모듈의 종류(Presentational 컴포넌트, Container 컴포넌트, Hook, 상수 등)에 따라 분류한 디렉토리 구조예요. +以下代码是按照模块类型(如 Presentational 组件、Container 组件、Hook、常量等)分类的目录结构。 ```text └─ src @@ -24,22 +24,22 @@ └─ ... ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 응집도 +### 内聚性 -파일을 이렇게 종류별로 나누면 어떤 코드가 어떤 코드를 참조하는지 쉽게 확인할 수 없어요. 코드 파일 사이의 의존 관계는 개발자가 스스로 코드를 분석하면서 챙겨야 해요. -또한 더 이상 특정 컴포넌트나 Hook, 유틸리티 함수가 사용되지 않아서 삭제된다고 했을 때, 연관된 코드가 함께 삭제되지 못해서 사용되지 않는 코드가 남아있게 될 수도 있어요. +如果按照这种方式按类划分文件,就很难确定代码之间的引用关系。代码文件之间的依赖关系需要开发者在分析代码时自行掌握和处理。 +此外,如果某个组件、Hook 或工具函数不再使用而被删除,相关代码可能未被同时删除,从而留下未使用代码。 -프로젝트의 크기는 점점 커지기 마련인데, 프로젝트의 크기가 2배, 10배, 100배 커짐에 따라서 코드 사이의 의존관계도 크게 복잡해질 수 있어요. 디렉토리 하나가 100개가 넘는 파일을 담고 있게 될 수도 있어요. +项目的规模往往会逐步扩大,随着项目规模的增加(比如 2 倍、10 倍甚至 100 倍),代码之间的依赖关系也将变得更加复杂。一个目录可能包含 100 多个文件。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음은 함께 수정되는 코드 파일끼리 하나의 디렉토리를 이루도록 구조를 개선한 예시예요. +以下是一个实例,展示了如何通过将需要一起修改的代码文件放在同一目录下,来优化项目结构。 ```text └─ src - │ // 전체 프로젝트에서 사용되는 코드 + │ // 整个项目中使用的代码 ├─ components ├─ containers ├─ hooks @@ -47,7 +47,7 @@ ├─ ... │ └─ domains - │ // Domain1에서만 사용되는 코드 + │ // 只在 Domain1 中使用的代码 ├─ Domain1 │ ├─ components │ ├─ containers @@ -55,7 +55,7 @@ │ ├─ utils │ └─ ... │ - │ // Domain2에서만 사용되는 코드 + │ // 只在 Domain2 中使用的代码 └─ Domain2 ├─ components ├─ containers @@ -64,14 +64,14 @@ └─ ... ``` -함께 수정되는 코드 파일을 하나의 디렉토리 아래에 둔다면, 코드 사이의 의존 관계를 파악하기 쉬워요. +将一起修改的代码文件放在同一目录下,很容易理解代码之间的依赖关系。 -예를 들어, 다음과 같이 한 도메인(`Domain1`)의 하위 코드에서 다른 도메인(`Domain2`)의 소스 코드를 참조한다고 생각해 볼게요. +例如,假设一个域(`Domain1`)的子代码中引用另一个域(`Domain2`)的源代码,如下所示: ```typescript import { useFoo } '../../../Domain2/hooks/useFoo' ``` -이런 import 문을 만난다면 잘못된 파일을 참조하고 있다는 것을 쉽게 인지할 수 있게 돼요. +如果遇到这样的 import 语句,就能很容易地意识到引用了错误的文件。 -또한, 특정 기능과 관련된 코드를 삭제할 때 한 디렉토리 전체를 삭제하면 깔끔하게 모든 코드가 삭제되므로, 프로젝트 내부에 더 이상 사용되지 않는 코드가 없도록 할 수 있어요. +此外,当删除与特定功能相关的代码时,只需删除整个目录即可一并清理所有相关代码,从而确保项目中不会遗留未使用的代码。 diff --git a/ch/code/examples/condition-name.md b/ch/code/examples/condition-name.md index 434fd150..4be4ae1a 100644 --- a/ch/code/examples/condition-name.md +++ b/ch/code/examples/condition-name.md @@ -1,40 +1,38 @@ -# 복잡한 조건에 이름 붙이기 +# 为复杂条件命名
- +
-복잡한 조건식이 특별한 이름 없이 사용되면, 조건이 뜻하는 바를 한눈에 파악하기 어려워요. +如果复杂的条件表达式没有特定的命名,就很难一眼理解其含义。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 상품 중에서 카테고리와 가격 범위가 일치하는 상품만 필터링하는 로직이에요. +下列代码实现了筛选类别和价格范围相匹配的商品的逻辑。 ```typescript const result = products.filter((product) => product.categories.some( (category) => category.id === targetCategory.id && - product.prices.some( - (price) => price >= minPrice && price <= maxPrice - ) + product.prices.some((price) => price >= minPrice && price <= maxPrice) ) ); ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -이 코드에서는 익명 함수와 조건이 복잡하게 얽혀 있어요. `filter`와 `some`, `&&` 같은 로직이 여러 단계로 중첩되어 있어서 정확한 조건을 파악하기 어려워졌어요. +在这段代码中,匿名函数和条件错综复杂。`filter`、`some`和`&&`等逻辑多层嵌套,导致很难确定正确的条件。 -코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아서, 가독성이 떨어져요. [^1] +代码阅读者需要考虑的上下文过多,导致可读性变差。[^1] -[^1]: [프로그래머의 뇌](https://www.yes24.com/product/goods/105911017)에 따르면, 사람의 뇌가 한 번에 저장할 수 있는 정보의 숫자는 6개라고 해요. +[^1]: [程序员超强大脑](https://www.yes24.com/product/goods/105911017)一书中提到,人的大脑一次性能够处理和存储的信息大约是 6 个。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음 코드와 같이 조건에 명시적인 이름을 붙이면, 코드를 읽는 사람이 한 번에 고려해야 할 맥락을 줄일 수 있어요. +以下代码展示了如何给条件加上明确的名称,以减少代码阅读者需要考虑的语境。 ```typescript const matchedProducts = products.filter((product) => { @@ -49,22 +47,22 @@ const matchedProducts = products.filter((product) => { }); ``` -명시적으로 같은 카테고리 안에 속해 있고, 가격 범위가 맞는 제품들로 필터링한다고 작성함으로써, 복잡한 조건식을 따라가지 않고도 코드의 의도를 명확히 드러낼 수 있어요. +通过明确命名筛选同类且价格范围的商品条件,可以避免追踪复杂的条件表达式,清晰表达代码的意图。 -## 🔍 더 알아보기: 조건식에 이름을 붙이는 기준 +## 🔍 深入了解: 为条件式命名的标准 -언제 조건식이나 함수에 이름을 붙이고 분리하는 것이 좋을까요? +什么时候适合给条件表达式或函数命名并将其提取? -### 조건에 이름을 붙이는 것이 좋을 때 +### 适合为条件命名的情况 -- **복잡한 로직을 다룰 때**: 조건문이나 함수에서 복잡한 로직이 여러 줄에 걸쳐 처리되면, 이름을 붙여 함수의 역할을 명확히 드러내는 것이 좋아요. 이렇게 하면 코드 가독성이 높아지고, 유지보수나 코드 리뷰가 더 쉬워져요. +- **处理复杂逻辑时**: 当条件语句或函数中的复杂逻辑跨越多行时,最好为其命名,明确展示函数的作用。这样可以提高代码可读性,维护和审查变得更加容易。 -- **재사용성이 필요할 때**: 동일한 로직을 여러 곳에서 반복적으로 사용할 가능성이 있으면, 변수나 함수를 선언해 재사용할 수 있어요. 이를 통해 코드 중복을 줄이고 유지보수가 더 쉬워져요. +- **需要重用时**: 如果同一逻辑可能在多个地方反复使用,可以通过声明变量或函数来实现重用。这样可以减少代码重复,便于后续的维护。 -- **단위 테스트가 필요할 때**: 함수를 분리하면 독립적으로 단위 테스트를 작성할 수 있어요. 단위 테스트는 함수가 올바르게 동작하는지 쉽게 확인할 수 있어, 복잡한 로직을 테스트할 때 특히 유용해요. +- **需要单元测试时**: 分离函数后,可以独立编写单元测试。单元测试可以轻松验证函数是否正常工作,尤其在测试复杂逻辑时非常实用。 -### 조건에 이름을 붙이지 않아도 괜찮을 때 +### 不需要为条件命名的情况 -- **로직이 간단할 때**: 로직이 매우 간단하면, 굳이 이름을 붙이지 않아도 돼요. 예를 들어, 배열의 요소를 단순히 두 배로 만드는 `arr.map(x => x * 2)`와 같은 코드는 이름을 붙이지 않아도 직관적이에요. +- **当逻辑简单时**: 如果逻辑非常简单,实际上不需要为其命名。例如,将数组中的元素翻倍的代码`arr.map(x => x * 2)`,即使不命名,也很直观。 -- **한 번만 사용될 때**: 특정 로직이 코드 내에서 한 번만 사용되며, 그 로직이 복잡하지 않으면 익명 함수에서 직접 로직을 처리하는 것이 더 직관적일 수 있어요. +- **当只使用一次时**: 如果某个逻辑在代码中只出现一次,而且逻辑并不复杂,那么在匿名函数中直接处理逻辑可能更加直观。 From ad15f991da7f41f06db4e38712323cefb8fbc0ee Mon Sep 17 00:00:00 2001 From: owonie Date: Mon, 27 Jan 2025 00:10:27 +0900 Subject: [PATCH 06/16] docs: translate into simplified chinese --- ch/code/examples/form-fields.md | 72 +++++++-------- ch/code/examples/hidden-logic.md | 26 +++--- ch/code/examples/http.md | 44 ++++----- ch/code/examples/item-edit-modal.md | 46 +++++----- ch/code/examples/login-start-page.md | 130 +++++++++++++-------------- ch/code/index.md | 2 +- 6 files changed, 160 insertions(+), 160 deletions(-) diff --git a/ch/code/examples/form-fields.md b/ch/code/examples/form-fields.md index 423da37c..0f2276cc 100644 --- a/ch/code/examples/form-fields.md +++ b/ch/code/examples/form-fields.md @@ -1,17 +1,17 @@ -# 폼의 응집도 생각하기 +# 考虑表单的内聚性
- +
-프론트엔드 개발을 하다 보면 Form으로 사용자에게 값을 입력받아야 하는 경우가 많아요. -Form을 관리할 때는 2가지의 방법으로 응집도를 관리해서, 함께 수정되어야 할 코드가 함께 수정되도록 할 수 있어요. +在前端开发中,经常需要用 Form 来获取用户输入的值。 +在管理 Form 时,可以通过两种方式来管理其内聚性,确保相关代码能同步修改。 -## 필드 단위 응집도 +## 字段级别的内聚性 -필드 단위 응집은 개별 입력 요소를 독립적으로 관리하는 방식이에요. -각 필드가 고유의 검증 로직을 가지므로 변경이 필요한 범위가 줄어들어 특정 필드의 유지보수가 쉬워져요. -필드 단위의 응집도를 고려하여 설계하면, 각 필드의 검증 로직이 독립적이어서 다른 필드에 영향을 주지 않아요. +字段级别的内聚是一种独立管理单个输入元素的方式。 +每个字段都拥有独立的验证逻辑,因此需要修改的范围会缩小,使得特定字段的维护变得更加容易。 +如果考虑字段级别的内聚性进行代码设计,则每个字段的验证逻辑相互独立,互不影响。 ```tsx import { useForm } from "react-hook-form"; @@ -29,7 +29,7 @@ export function Form() { }); const onSubmit = handleSubmit((formData) => { - // 폼 데이터 제출 로직 + // 表单数据提交逻辑 console.log("Form submitted:", formData); }); @@ -39,9 +39,9 @@ export function Form() { - isEmptyStringOrNil(value) ? "이름을 입력해주세요." : "" + isEmptyStringOrNil(value) ? "请输入您的姓名。" : "" })} - placeholder="이름" + placeholder="姓名" /> {errors.name &&

{errors.name.message}

} @@ -51,22 +51,22 @@ export function Form() { {...register("email", { validate: (value) => { if (isEmptyStringOrNil(value)) { - return "이메일을 입력해주세요."; + return "请输入您的电子邮件。"; } if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(value)) { - return "유효한 이메일 주소를 입력해주세요."; + return "请输入有效的电子邮件。"; } return ""; } })} - placeholder="이메일" + placeholder="电子邮件" /> {errors.email &&

{errors.email.message}

} - + ); } @@ -82,11 +82,11 @@ function isEmptyStringOrNil(value: NullableString): boolean { } ``` -## 폼 전체 단위 응집도 +## 表单级别的内聚性 -폼 전체 응집은 모든 필드의 검증 로직이 폼에 종속되는 방식이에요. 폼 전체에서의 흐름을 고려하여 설계되며, 변경 단위가 폼 단위로 발생할 때 고려해요. +表单级别的内聚是一种所有字段的验证逻辑依赖于整个表单的方式。它是根据整个表单的流程来设计的,通常在表单级别发生更改时加以考虑。 -폼 전체 응집도를 높이면, 폼 전체의 검증이 한 곳에서 관리되어 로직이 간결해지고, 상태가 중앙 집중식으로 관리되므로 폼 전체 흐름을 이해하기 쉬워져요. 필드 간의 결합도가 높아지므로 폼의 재사용성은 떨어질 수 있어요. +提高表单的整体内聚性后,所有验证将统一管理,逻辑更加简单明了,执行集中式状态管理,更容易理解表单的整体流程。然而,字段之间的耦合性会提高,从而降低表单的可重用性。 ```tsx import * as z from "zod"; @@ -94,11 +94,11 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; const schema = z.object({ - name: z.string().min(1, "이름을 입력해주세요."), + name: z.string().min(1, "请输入您的姓名。"), email: z .string() - .min(1, "이메일을 입력해주세요.") - .email("유효한 이메일 주소를 입력해주세요") + .min(1, "请输入您的电子邮件。") + .email("请输入有效的电子邮件。") }); export function Form() { @@ -115,42 +115,42 @@ export function Form() { }); const onSubmit = handleSubmit((formData) => { - // 폼 데이터 제출 로직 + // 表单数据提交逻辑 console.log("Form submitted:", formData); }); return (
- + {errors.name &&

{errors.name.message}

}
- + {errors.email &&

{errors.email.message}

}
- +
); } ``` -## 필드 단위 vs. 폼 전체 단위 응집도 +## 字段级别 vs. 表单级别 内聚性 -응집도를 높이려면 필드 단위와 폼 전체 단위 중 상황에 적합한 방식을 선택해야 해요. -필드 단위로 나누면 재사용성과 독립성이 높아지지만, 폼 전체 단위로 관리하면 일관된 흐름을 유지할 수 있어요. +欲提高内聚性,需要在字段级别和表单级别选择适合场景下的方式。 +按字段级别进行划分,可提高可重用性和独立性;但如果以整个表单单位进行管理,则可以保持一致性。 -변경의 단위가 필드 단위인지 폼 전체 단위인지에 따라 설계를 조정해야 해요. +我们需要根据变更的单位时字段级别还是整个表单级别来调整设计。 -### 필드 단위 응집도를 선택하면 좋을 때 +### 适合选择字段级别内聚性的情况 -- **독립적인 검증이 필요할 때**: 필드별로 복잡한 검증 로직이 필요하거나 비동기 검증이 필요한 경우예요. 이메일 형식 검사, 전화번호 유효성 검증, 아이디 중복 확인, 추천 코드 유효성 확인처럼 각 필드가 독립적이고 고유한 검증이 필요할 때 유용해요. -- **재사용이 필요할 때**: 필드와 검증 로직이 다른 폼에서도 동일하게 사용될 수 있는 경우예요. 공통 입력 필드들을 독립적으로 관리하고 재사용하고 싶을 때 좋아요. +- **需要独立验证时**:这是指对每个字段进行复杂的验证逻辑,或者需要异步验证的情况。当每个字段需要独立且独特的验证时,例如电子邮件格式检查、电话号码有效性验证、ID 重复确认、推荐码有效验证等,这种方法会十分有用。 +- **需要考虑可重用性时**:这是指字段和验证逻辑可以在其他表单中同一使用的情况。若需独立管理公共输入字段,此方案颇为适宜。 -### 폼 전체 단위 응집도를 선택하면 좋을 때 +### 适合选择表单级别内聚性的情况 -- **단일 기능을 나타낼 때**: 모든 필드가 밀접하게 관련되어 하나의 완결된 기능을 구성하는 경우예요. 결제 정보나 배송 정보처럼 모든 필드가 하나의 비즈니스 로직을 이룰 때 유용해요. -- **단계별 입력이 필요할 때**: Wizard Form과 같이 스텝별로 동작하는 복잡한 폼의 경우예요. 회원가입이나 설문조사처럼 이전 단계의 입력값이 다음 단계에 영향을 주는 경우에 적합해요. -- **필드 간 의존성이 있을 때**: 여러 필드가 서로를 참조하거나 영향을 주는 경우예요. 비밀번호 확인이나 총액 계산처럼 필드 간 상호작용이 필요할 때 좋아요. +- **表示单一功能时**:这是所有字段都紧密相关,共同构成一个完整功能的情况。当所有字段共同构成一个业务逻辑时(如:支付信息、配送信息等),此方案颇为适宜。 +- **需要逐步输入时**:比如像 Wizard Form(漏斗表单)一样,分步骤操作的复杂表单的情况。它适用于像会员注册或问卷调查那样,前一阶段的输入值会影响下一阶段的情况。 +- **字段之间存在依赖关系时**:适用于多个字段相互引用或相互影响的情况。需要字段间交互时,例如密码确认或总额计算的情况下实用。 diff --git a/ch/code/examples/hidden-logic.md b/ch/code/examples/hidden-logic.md index 57c19148..105ff76b 100644 --- a/ch/code/examples/hidden-logic.md +++ b/ch/code/examples/hidden-logic.md @@ -1,14 +1,14 @@ -# 숨은 로직 드러내기 +# 揭示隐藏的逻辑
- +
-함수나 컴포넌트의 이름, 파라미터, 반환 값에 드러나지 않는 숨은 로직이 있다면, 함께 협업하는 동료들이 동작을 예측하는 데에 어려움을 겪을 수 있어요. +如果函数或组件的名称、参数、返回值中存在未明确表达的隐藏逻辑,那么与你合作的同时可能会难以预测其行为。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 사용자의 계좌 잔액을 조회할 때 사용할 수 있는 `fetchBalance` 함수예요. 함수를 호출할 때마다 암시적으로 `balance_fetched`라는 로깅이 이루어지고 있어요. +下面的代码是一个名为 `fetchBalance` 的函数,用于查询用户的账户余额。每次调用函数时,都会隐式地启动名为 `balance_fetched` 的日志函数。 ```typescript 4 async function fetchBalance(): Promise { @@ -20,17 +20,17 @@ async function fetchBalance(): Promise { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 예측 가능성 +### 可预测性 -`fetchBalance` 함수의 이름과 반환 타입만을 가지고는 `balance_fetched` 라는 로깅이 이루어지는지 알 수 없어요. 그래서 로깅을 원하지 않는 곳에서도 로깅이 이루어질 수 있어요. +仅根据 `fetchBalance` 函数的名称和返回类型,无法得知是否会记录名为 `balance_fetched` 的日志。因此,即使在不需要日志记录的地方,也可能会触发日志记录。 -또, 로깅 로직에 오류가 발생했을 때 갑자기 계좌 잔액을 가져오는 로직이 망가질 수도 있죠. +另外,如果日志记录逻辑出错,获取账户余额的功能也可能突然失效。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -함수의 이름과 파라미터, 반환 타입으로 예측할 수 있는 로직만 구현 부분에 남기세요. +请仅在实现部分保留可以通过函数名、参数和返回类型来预测的逻辑。 ```typescript async function fetchBalance(): Promise { @@ -40,7 +40,7 @@ async function fetchBalance(): Promise { } ``` -로깅을 하는 코드는 별도로 분리하세요. +请将日志记录的代码单独分离出来。 ```tsx ``` diff --git a/ch/code/examples/http.md b/ch/code/examples/http.md index ca26378f..7d094f81 100644 --- a/ch/code/examples/http.md +++ b/ch/code/examples/http.md @@ -1,20 +1,20 @@ -# 이름 겹치지 않게 관리하기 +# 避免命名重复
- +
-같은 이름을 가지는 함수나 변수는 동일한 동작을 해야 해요. 작은 동작 차이가 코드의 예측 가능성을 낮추고, 코드를 읽는 사람에게 혼란을 줄 수 있어요. +具有相同名称的函数或变量应该具有相同的行为。微小的行为差异会降低代码的可预测性,并可能使代码阅读者感到困惑。 -## 📝 코드 예시 +## 📝 代码示例 -어떤 프론트엔드 서비스에서 원래 사용하던 HTTP 라이브러리를 감싸서 새로운 형태로 HTTP 요청을 보내는 모듈을 만들었어요. -공교롭게 원래 HTTP 라이브러리와 새로 만든 HTTP 모듈의 이름은 `http`로 같아요. +在某个前端服务中,通过封装原本使用的 HTTP 库,创建了一个以新形式发送 HTTP 请求的模块。 +巧合的是,原本的 HTTP 库和新创建的 HTTP 模块名称相同,都叫 `http` 。 ::: code-group ```typescript [http.ts] -// 이 서비스는 `http`라는 라이브러리를 쓰고 있어요 +// 该服务使用 `http` 库。 import { http as httpLibrary } from "@some-library/http"; export const http = { @@ -27,7 +27,7 @@ export const http = { ``` ```typescript [fetchUser.ts] -// http.ts에서 정의한 http를 가져오는 코드 +// 从 http.ts 文件中导入 http 的代码 import { http } from "./http"; export async function fetchUser() { @@ -37,30 +37,30 @@ export async function fetchUser() { ::: -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 예측 가능성 +### 可预测性 -이 코드는 기능적으로 문제가 없지만, 읽는 사람에게 혼란을 줄 수 있어요. `http.get`을 호출하는 개발자는 이 함수가 원래의 HTTP 라이브러리가 하는 것처럼 단순한 GET 요청을 보내는 것으로 예상하지만, 실제로는 토큰을 가져오는 추가 작업이 수행돼요. +这段代码在功能上没有问题,但可能会让代码阅读者感到困惑。调用 `http.get` 的开发者可能会预期这个函数像原始的 HTTP 库一样只是发送一个简单的 GET 请求,但实际上会执行额外的操作,如获取令牌。 -오해로 인해서 기대 동작과 실제 동작의 차이가 생기고, 버그가 발생하거나, 디버깅 과정을 복잡하고 혼란스럽게 만들 수 있어요. +由于误解,预期行为与实际行为之间会出现差异,从而引发错误,或者使调试过程变得复杂和混乱。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -서비스에서 만든 함수에는 라이브러리의 함수명과 구분되는 명확한 이름을 사용해서 함수의 동작을 예측 가능하게 만들 수 있어요. +为了提高函数行为的可预测性,在服务中自定义函数时,应该使用与库函数明显区分开来的、具有描述行的名称。 ::: code-group ```typescript [httpService.ts] -// 이 서비스는 `http`라는 라이브러리를 쓰고 있어요 +// 该服务正在使用 `http` 库。 import { http as httpLibrary } from "@some-library/http"; -// 라이브러리 함수명과 구분되도록 명칭을 변경했어요. +// 将库函数的名称与自定义函数区分开来。 export const httpService = { async getWithAuth(url: string) { const token = await fetchToken(); - // 토큰을 헤더에 추가하는 등 인증 로직을 추가해요. + // 添加认证逻辑,例如在请求头中添加令牌。 return httpLibrary.get(url, { headers: { Authorization: `Bearer ${token}` } }); @@ -69,18 +69,18 @@ export const httpService = { ``` ```typescript [fetchUser.ts] -// http.ts에서 정의한 http를 가져오는 코드 +// 从 http.ts 文件中引入定义的 http 模块 import { httpService } from "./httpService"; export async function fetchUser() { - // 함수명을 통해 이 함수가 인증된 요청을 보내는 것을 알 수 있어요. + // 通过函数名,可知该函数发送的是已通过认证的请求。 return await httpService.getWithAuth("..."); } ``` ::: -이렇게 해서 함수의 이름을 봤을 때 동작을 오해할 수 있는 가능성을 줄일 수 있어요. -다른 개발자가 이 함수를 사용할 때, 서비스에서 정의한 함수라는 것을 인지하고 올바르게 사용할 수 있어요. +这种方式可以减少在看到函数名时对其功能产生误解的可能性。 +当其他开发者使用该函数时,他们能够意识到这是服务中定义的函数,并能够正确的使用它。 -또한, `getWithAuth`라는 이름으로 이 함수가 인증된 요청을 보낸다는 것을 명확하게 전달할 수 있어요. +另外,通过 `getWithAuth` 这个名称,可以明确传达该函数是用来发送通过认证的请求。 diff --git a/ch/code/examples/item-edit-modal.md b/ch/code/examples/item-edit-modal.md index 81123974..febecba7 100644 --- a/ch/code/examples/item-edit-modal.md +++ b/ch/code/examples/item-edit-modal.md @@ -1,23 +1,23 @@ -# Props Drilling 지우기 +# 消除 Props Drilling
- +
-Props Drilling은 부모 컴포넌트와 자식 컴포넌트 사이에 결합도가 생겼다는 것을 나타내는 명확한 표시예요. 만약에 Drilling되는 프롭이 변경되면, 프롭을 참조하는 모든 컴포넌트가 변경되어야 하죠. +Props Drilling(属性钻探)是父组件与子组件之间产生耦合的一个明显迹象。如果正在被钻取的属性发生了变化,那么所有引用该属性的组件都需要更新。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 사용자가 `item`을 선택할 때 사용하는 `` 컴포넌트예요. -사용자가 키워드를 입력해서 아이템 목록을 검색하고, 찾고 있었던 아이템을 선택하면 `onConfirm`이 호출돼요. +以下代码是用户选择 `item` 时使用的 `` 组件。 +用户输入关键词来搜索项目列表,当找到目标项目并选择时, 会调用 `onConfirm` 函数。 -사용자가 입력한 키워드는 `keyword`, 선택할 수 있는 아이템은 `items`, 추천 아이템의 목록은 `recommendedItems` 프롭으로 전달돼요. +用户输入的关键词通过 `keyword` 传递,可供选择的项目通过 `items` 传递,推荐项目列表通过 `recommendedItems` 属性传递。 ```tsx 2,9-10,12-13,39-42 function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) { const [keyword, setKeyword] = useState(""); - // 다른 ItemEditModal 로직 ... + // 其他 ItemEditModal 逻辑 ... return ( @@ -29,7 +29,7 @@ function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) { onConfirm={onConfirm} onClose={onClose} /> - {/* ... 다른 ItemEditModal 컴포넌트 ... */} + {/* ... 其他 ItemEditModal 组件 ... */} ); } @@ -44,12 +44,12 @@ function ItemEditBody({ }) { return ( <> -
+
onKeywordChange(e.target.value)} /> - +
-
+
onKeywordChange(e.target.value)} /> - +
- +
-한 사람이 코드를 읽을 때 동시에 고려할 수 있는 총 맥락의 숫자는 제한되어 있다고 해요. -내 코드를 읽는 사람들이 코드를 쉽게 읽을 수 있도록 하기 위해서 불필요한 맥락을 추상화할 수 있어요. +一个人在阅读代码时,能够同时考虑的上下文总数有限。 +为了让阅读者轻松理解你的代码,可以将不必要的语境进行抽象化处理。 -## 📝 코드 예시 1: LoginStartPage +## 📝 代码示例 1: LoginStartPage -다음 `` 컴포넌트는 사용자가 로그인되었는지 확인하고, 로그인이 된 경우 홈으로 이동시키는 로직을 가지고 있어요. +以下 `` 组件包含检查用户是否已登录的逻辑,如果已登录,则会将用户重定向到主页。 ```tsx function LoginStartPage() { @@ -21,28 +21,28 @@ function LoginStartPage() { } }); - /* ... 로그인 관련 로직 ... */ + /* ... 登录相关逻辑 ... */ - return <>{/* ... 로그인 관련 컴포넌트 ... */}; + return <>{/* ... 登录相关组件 ... */}; } ``` -### 👃 코드 냄새 맡아보기 +### 👃 闻代码 -#### 가독성 +#### 可读性 -예시 코드에서는 로그인이 되었는지 확인하고, 사용자를 홈으로 이동시키는 로직이 추상화 없이 노출되어 있어요. 그래서 `useCheckLogin`, `onChecked`, `status`, `"LOGGED_IN"`과 같은 변수나 값을 모두 읽어야 무슨 역할을 하는 코드인지 알 수 있어요. +在示例代码中,检查用户是否已登录以及将用户重定向到主页的逻辑没有被抽象化,而是直接显露出来。所以,你需要读懂像 `useCheckLogin` 、 `onChecked` 、 `status` 以及 `"LOGGED_IN"` 这样的变量和值,才能明白这段代码的作用。 -이 코드와 더불어서, 실제로 로그인과 관련된 코드가 밑에 이어지는데요. 읽는 사람이 `LoginStartPage`가 무슨 역할을 하는지 알기 위해서 한 번에 이해해야 하는 맥락이 많아요. +除了这段代码,下面还紧接着有与实际登录相关的代码。阅读者为了理解 `LoginStartPage` 的作用,需要一次性理解很多上下文信息。 -### ✏️ 개선해보기 +### ✏️ 尝试改善 -사용자가 로그인되었는지 확인하고 이동하는 로직을 **HOC(Higher-Order Component)** 나 Wrapper 컴포넌트로 분리하여, 코드를 읽는 사람이 한 번에 알아야 하는 맥락을 줄여요. -그래서 코드의 가독성을 높일 수 있어요. +通过将检查用户是否登录并进行导航的逻辑提取到 **HOC(高阶组件)** 或 Wrapper 组件中,可以减少代码阅读者一次性需要理解的上下文。 +从而可以提高代码的可读性。 -또한, 분리된 컴포넌트 안에 있는 로직끼리 참조를 막음으로써, 코드 간의 불필요한 의존 관계가 생겨서 복잡해지는 것을 막을 수 있어요. +此外,通过阻止不同组件中的逻辑相互引用,可以避免代码之间产生不必要地依赖关系,防止代码变得过于复杂。 -#### 옵션 A: Wrapper 컴포넌트 사용하기 +#### 选项 A: 使用 Wrapper 组件 ```tsx function App() { @@ -66,24 +66,24 @@ function AuthGuard({ children }) { } function LoginStartPage() { - /* ... 로그인 관련 로직 ... */ + /* ... 登录相关逻辑 ... */ - return <>{/* ... 로그인 관련 컴포넌트 ... */}; + return <>{/* ... 登录相关组件 ... */}; } ``` -#### 옵션 B: HOC(Higher-Order Component) 사용하기 +#### 选项 B: 使用 HOC(高阶组件) ```tsx function LoginStartPage() { - /* ... 로그인 관련 로직 ... */ + /* ... 登录相关逻辑 ... */ - return <>{/* ... 로그인 관련 컴포넌트 ... */}; + return <>{/* ... 登录相关组件 ... */}; } export default withAuthGuard(LoginStartPage); -// HOC 정의 +// 高阶组件的定义 function withAuthGuard(WrappedComponent) { return function AuthGuard(props) { const status = useCheckLoginStatus(); @@ -99,31 +99,31 @@ function withAuthGuard(WrappedComponent) { } ``` -## 📝 코드 예시 2: FriendInvitation +## 📝 代码示例 2: FriendInvitation -다음 `` 컴포넌트는 클릭하면 사용자에게 동의를 받고 사용자에게 초대를 보내는 페이지 컴포넌트예요. +以下 `` 是一个被点击时,会向用户征求同意,并给用户发送邀请的页面组件。 ```tsx 6-27,33 function FriendInvitation() { - const { data } = useQuery(/* 생략.. */); + const { data } = useQuery(/* 省略.. */); - // 이외 이 컴포넌트에 필요한 상태 관리, 이벤트 핸들러 및 비동기 작업 로직... + // 该组件需要的状态管理、事件处理程序及异步操作等逻辑... const handleClick = async () => { const canInvite = await overlay.openAsync(({ isOpen, close }) => ( close(false)}> - 닫기 + 关闭 } confirmButton={ close(true)}> - 확인 + 确认 } - /* 중략 */ + /* 中略 */ /> )); @@ -132,45 +132,45 @@ function FriendInvitation() { } }; - // 이외 이 컴포넌트에 필요한 상태 관리, 이벤트 핸들러 및 비동기 작업 로직... + // 该组件需要的状态管理、事件处理程序及异步操作等逻辑... return ( <> - - {/* UI를 위한 JSX 마크업... */} + + {/* 用于用户界面的 JSX 标记... */} ); } ``` -### 👃 코드 냄새 맡아보기 +### 👃 闻代码 -#### 가독성 +#### 可读性 -가독성을 지키려면 코드가 한 번에 가지고 있는 맥락이 적어야 해요. 하나의 컴포넌트가 가지고 있는 맥락이 다양하면 컴포넌트의 역할을 한눈에 파악하기 어려워져요. +为了保持可读性,代码同时承载的上下文越少越好。如果一个组件包含太多的语境,就很难一眼看出该组件的作用。 -`` 컴포넌트는 실제로 사용자에게 동의를 받을 때 사용하는 자세한 로직까지 하나의 컴포넌트에 가지고 있어요. 그래서 코드를 읽을 때 따라가야 할 맥락이 많아서 읽기 어려워요. +`` 这一组件内实际上包含了向用户获取同意等详细的逻辑。所以在阅读代码时,需要追踪过多的语境,导致阅读难度高。 -#### 응집도 +#### 内聚性 -사용자에게 동의를 받는 로직과 실제로 그 로직을 실행하는 로직인 ` ); } ``` -`` 컴포넌트는 사용자를 초대하는 로직과 UI만 가지고 있으므로, 한 번에 인지해야 하는 내용을 적게 유지해서 가독성을 높일 수 있어요. 또한, 버튼과 클릭 후 실행되는 로직이 아주 가까이에 있어요. +`` 组件仅包含邀请用户的逻辑和 UI, 因此能够减少需要一次性认知的信息量,提高可读性。而且,按钮与点击后执行的逻辑紧密相连。 -## 🔍 더 알아보기: 추상화 +## 🔍 深入了解: 抽象化 -토스 기술 블로그의 [선언적인 코드 작성하기](https://toss.tech/article/frontend-declarative-code) 문서에서는 코드를 글로 비유해요. +在 Toss 的技术博客 [编写声明式代码](https://toss.tech/article/frontend-declarative-code) 一文中,将代码比作了文章。 -### 글에서 추상화 +### 文章中的抽象化 -"왼쪽으로 10걸음 걸어라" 라고 하는 문장이 있어요. 여기에서 +有一句话是“向左走十步”。 在这句话里 -- “왼쪽”은 “북쪽을 바라보았을 때 90도 돌아간 위치” 를 추상화한 것이고, -- “90도”는 “한 번의 회전을 360등분한 각의 90배만큼 시초선에 대해 시계 반대 방향으로 돌아간 것” 을 추상화한 것이고, -- “시계 방향” 의 정의는 “북반구에서 해시계의 바늘이 돌아가는 방향” 을 추상화한 것이에요. +- ”向左“是”面向北方时旋转 90 度的位置“的抽象概念, +- ”90 度“是”将一个完整的旋转(360 度)等分后,取其中 90 份,即相对于初始线逆时针方向旋转的角度“的抽象概念, +- ”时针方向“的定义是”北半球上日晷的指针转动的方向“的抽象概念。 -비슷하게 "10걸음", "걸어라"와 같은 단어도 보다 구체적으로 표현할 수 있어요. 그래서 추상화 없이 그대로 문장을 나타낸다면, 다음과 같이 나타낼 수 있을 거예요. +与此类似,”十步“、”走“这样的词汇也可以更具体的表达。因此,不进行抽象化而直接表述的句子可能会是这样: -> 북쪽을 바라보았을 때 한 번의 회전을 360등분한 각의 90배만큼 북반구에서 해시계의 바늘이 돌아가는 방향으로 돌아서, 동물이 육상에서 다리를 이용해 움직이는 가장 빠른 방법보다 느린, 신체를 한 지점에서 다른 지점으로 옮겨가는 행위를 10번 반복해라 +> 面朝北方,以 1 次旋转的角度 360 等分后乘于 90 倍的度数,在北半球中沿日晷指针转动的方向转动,然后重复将身体从一点移动到另一点的动作(比动物在陆地上用腿移动的最快方式还慢的行为)10 次。 -이 문장은 그대로 읽었을 때 어떤 의미인지 정확하게 파악하기 어려워요. +直接阅读该文章时很难准确理解其真正意图。 -### 코드에서 추상화 +### 代码中的抽象化 -비슷하게 코드에서도 구현 상세를 지나치게 드러내는 경우, 이 코드가 어떤 역할을 하는지 정확하게 파악하기 어려워요. -한 번에 6~7개 정도의 맥락을 한 번에 고려해 가면서 읽을 수 있도록, 보다 작은 단위로 추상화하는 것이 필요해요. +同样地,编程时如果显露过多实现细节,就很难把握代码的真正用途。 +为了让代码阅读时能同时兼顾六到七个不同语境,需要将这些语境抽象为更小的单位。 diff --git a/ch/code/index.md b/ch/code/index.md index 091833ef..cefc55f5 100644 --- a/ch/code/index.md +++ b/ch/code/index.md @@ -43,7 +43,7 @@ comments: false **内聚性**(Cohesion)是指需要被修改的代码是否总是一起修改的特性。 内聚性高的代码,即使修改了某一部分,也不会在其他部分引发障碍。 -这是因为在结构上确保了需要修改的代码一同修改。 +这是因为在结构上保证了相关代码能够同步修改。 ::: info 可读性与内聚性可能存在冲突 From 3b7e9168f491feb7efb17235acc0bffd8e53a8d3 Mon Sep 17 00:00:00 2001 From: owonie Date: Mon, 27 Jan 2025 02:17:12 +0900 Subject: [PATCH 07/16] docs: translate into simplified chinese --- ch/code/examples/magic-number-cohesion.md | 28 +++++------ ch/code/examples/magic-number-readability.md | 39 ++++++++------- ch/code/examples/submit-button.md | 36 +++++++------- ch/code/examples/ternary-operator.md | 28 +++++------ ch/code/examples/use-bottom-sheet.md | 50 ++++++++++---------- 5 files changed, 89 insertions(+), 92 deletions(-) diff --git a/ch/code/examples/magic-number-cohesion.md b/ch/code/examples/magic-number-cohesion.md index a668d32d..d66c834a 100644 --- a/ch/code/examples/magic-number-cohesion.md +++ b/ch/code/examples/magic-number-cohesion.md @@ -1,17 +1,16 @@ -# 매직 넘버 없애기 +# 消除魔数
- +
-**매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요. +**魔数**(Magic Number)指的是缺乏明确说明而直接插入的数值。 -예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나, -하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요. +例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一整天的时间。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요. +下列代码是一个函数,当点击点赞按钮时重新获取点赞数量。 ```typescript 3 async function onLikeClick() { @@ -21,24 +20,23 @@ async function onLikeClick() { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 응집도 +### 内聚性 -`300`이라고 하는 숫자를 애니메이션 완료를 기다리려고 사용했다면, 재생하는 애니메이션을 바꿨을 때 조용히 서비스가 깨질 수 있는 위험성이 있어요. -충분한 시간동안 애니메이션을 기다리지 않고 바로 다음 로직이 시작될 수도 있죠. +如果使用像 `300` 这样的固定时间值来等待动画完成,那么在动画播放的过程中进行更改时,服务可能会悄无声息地出现故障。因为后续的逻辑可能会在动画还未完成时就开始执行。 -같이 수정되어야 할 코드 중 한쪽만 수정된다는 점에서, 응집도가 낮은 코드라고도 할 수 있어요. +此外,由于只修改了需要同步更改的代码中的一部分,这段代码的内聚性很低。 ::: info -이 Hook은 [가독성](./magic-number-readability.md) 관점으로도 볼 수 있어요. +这个 Hook 也可以从 [可读性](./magic-number-readability.md) 的角度来考虑。 ::: -## ✏️ 개선해보기 +## ✏️ 尝试改善 -숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요. +为了更准确的表达数字 `300` 的含义,可以将其声明为常量 `ANIMATION_DELAY_MS` 。 ```typescript 1,5 const ANIMATION_DELAY_MS = 300; diff --git a/ch/code/examples/magic-number-readability.md b/ch/code/examples/magic-number-readability.md index 5adf5ef1..77413257 100644 --- a/ch/code/examples/magic-number-readability.md +++ b/ch/code/examples/magic-number-readability.md @@ -1,17 +1,16 @@ -# 매직 넘버에 이름 붙이기 +# 为魔数命名
- +
-**매직 넘버**(Magic Number)란 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것을 말해요. +**魔数**(Magic Number)指的是缺乏明确说明而直接插入的数值。 -예를 들어, 찾을 수 없음(Not Found)을 나타내는 HTTP 상태 코드로 `404` 값을 바로 사용하는 것이나, -하루를 나타내는 `86400`초를 그대로 사용하는 것이 있어요. +例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一整天的时间。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 좋아요 버튼을 눌렀을 때 좋아요 개수를 새로 내려받는 함수예요. +下列代码是一个函数,当点击点赞按钮时重新获取点赞数量。 ```typescript 3 async function onLikeClick() { @@ -21,28 +20,28 @@ async function onLikeClick() { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -이 코드는 `delay` 함수에 전달된 `300`이라고 하는 값이 어떤 맥락으로 쓰였는지 알 수 없어요. -원래 코드를 작성한 개발자가 아니라면, 어떤 목적으로 300ms동안 기다리는지 알 수 없죠. +这段代码中的 `delay` 函数传递了一个值 `300` ,但无法从上下文推测该值的具体用途。 +如果不是该代码的编写者,就无法理解 300ms 等待的是什么。 -- 애니메이션이 완료될 때까지 기다리는 걸까? -- 좋아요 반영에 시간이 걸려서 기다리는 걸까? -- 테스트 코드였는데, 깜빡하고 안 지운 걸까? +- 是在等待动画完成? +- 是在等待点赞反映时间? +- 是不是忘了删测试代码? -하나의 코드를 여러 명의 개발자가 함께 수정하다 보면 의도를 정확히 알 수 없어서 코드가 원하지 않는 방향으로 수정될 수도 있어요. +当多名开发者共同修改同一段代码时,可能无法明确原意,从而导致代码被修改成不符合预期的结果。 ::: info -이 Hook은 [응집도](./magic-number-cohesion.md) 관점으로도 볼 수 있어요. +这个 Hook 也可以从 [内聚性](./magic-number-cohesion.md) 的角度来考虑。 ::: -## ✏️ 개선해보기 +## ✏️ 尝试改善 -숫자 `300`의 맥락을 정확하게 표시하기 위해서 상수 `ANIMATION_DELAY_MS`로 선언할 수 있어요. +为了更准确的表达数字 `300` 的含义,可以将其声明为常量 `ANIMATION_DELAY_MS` 。 ```typescript 1,5 const ANIMATION_DELAY_MS = 300; @@ -54,6 +53,6 @@ async function onLikeClick() { } ``` -## 🔍 더 알아보기 +## 🔍 深入了解 -매직 넘버는 응집도 관점에서도 살펴볼 수 있어요. [매직 넘버 없애서 응집도 높이기](./magic-number-cohesion.md) 문서도 참고해 보세요. +魔数也可以从内聚性角度来审视。请参考 [消除魔数提高内聚性](./magic-number-cohesion.md) 一文。 diff --git a/ch/code/examples/submit-button.md b/ch/code/examples/submit-button.md index 4903e181..6714ee2a 100644 --- a/ch/code/examples/submit-button.md +++ b/ch/code/examples/submit-button.md @@ -1,18 +1,18 @@ -# 같이 실행되지 않는 코드 분리하기 +# 拆分不同语境下的代码
- +
-동시에 실행되지 않는 코드가 하나의 함수 또는 컴포넌트에 있으면, 동작을 한눈에 파악하기 어려워요. -구현 부분에 많은 숫자의 분기가 들어가서, 어떤 역할을 하는지 이해하기 어렵기도 해요. +如果非同步执行的代码被放在同一个函数或组件中,就很难一眼看清他们各自的作用。 +由于实现过程中充满了复杂的分支,使得很难理解每个部分的作用。 -## 📝 코드 예시 +## 📝 代码示例 -다음 `` 컴포넌트는 사용자의 권한에 따라서 다르게 동작해요. +`` 组件会根据用户的权限以不同的方式运行。 -- 사용자의 권한이 보기 전용(`"viewer"`)이면, 초대 버튼은 비활성화되어 있고, 애니메이션도 재생하지 않아요. -- 사용자가 일반 사용자이면, 초대 버튼을 사용할 수 있고, 애니메이션도 재생해요. +- 如果用户的权限是仅查看(`"viewer"`),邀请按钮会处于非激活状态,不会播放动画。 +- 如果用户是普通用户,邀请按钮处于激活状态,并且播放动画。 ```tsx function SubmitButton() { @@ -33,21 +33,21 @@ function SubmitButton() { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -`` 컴포넌트에서는 사용자가 가질 수 있는 2가지의 권한 상태를 하나의 컴포넌트 안에서 한 번에 처리하고 있어요. -그래서 코드를 읽는 사람이 한 번에 고려해야 하는 맥락이 많아요. +`` 组件同时处理用户可能具有的两种权限状态,且该两种状态都在同一组件中进行处理。 +所以代码阅读者需要考虑的语境过多。 -예를 들어, 다음 코드에서 파란색은 사용자가 보기 전용 권한(`'viewer'`)을 가지고 있을 때, 빨간색은 일반 사용자일 때 실행되는 코드예요. -동시에 실행되지 않는 코드가 교차되어서 나타나서 코드를 이해할 때 부담을 줘요. +例如,在下面的代码中,蓝色部分表示当用户具有仅查看权限(`'viewer'`)时执行的代码,红色部分表示当用户是普通用户时执行的代码。 +由于不同时执行的代码交织在一起,这给理解代码带来了负担。 ![](../../images/examples/submit-button.png) -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음 코드는 사용자가 보기 전용 권한을 가질 때와 일반 사용자일 때를 완전히 나누어서 관리하도록 하는 코드예요. +以下代码是将用户具有仅查看权限时和作为普通用户时的状态完全分开来管理的代码示例。 ```tsx function SubmitButton() { @@ -69,5 +69,5 @@ function AdminSubmitButton() { } ``` -- `` 코드 곳곳에 있던 분기가 단 하나로 합쳐지면서, 분기가 줄어들었어요. -- ``과 `` 에서는 하나의 분기만 관리하기 때문에, 코드를 읽는 사람이 한 번에 고려해야 할 맥락이 적어요. +- 随着原本分散在 `` 代码各处的分支合并为一,分支数量减少。 +- `` 和 `` 各自值管理一个分支,所以代码阅读者需要考虑的语境减少。 diff --git a/ch/code/examples/ternary-operator.md b/ch/code/examples/ternary-operator.md index 2eb7ed7c..7d119869 100644 --- a/ch/code/examples/ternary-operator.md +++ b/ch/code/examples/ternary-operator.md @@ -1,35 +1,35 @@ -# 삼항 연산자 단순하게 하기 +# 简化三元运算符
- +
-삼항 연산자를 복잡하게 사용하면 조건의 구조가 명확하게 보이지 않아서 코드를 읽기 어려울 수 있어요. +复杂地使用三元运算符可能会掩盖条件的结构,从而使代码难以阅读。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드는 `A조건`과 `B조건`에 따라서 `"BOTH"`, `"A"`, `"B"` 또는 `"NONE"` 중 하나를 `status`에 지정하는 코드예요. +以下代码根据 `条件A` 和 `条件B`,将 `status` 设置为 `"BOTH"`、 `"A"` 、 `"B"` 或 `"NONE"` 中的一个。 ```typescript const status = - (A조건 && B조건) ? "BOTH" : (A조건 || B조건) ? (A조건 ? "A" : "B") : "NONE"; + 条件A && 条件B ? "BOTH" : 条件A || 条件B ? (条件A ? "A" : "B") : "NONE"; ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -이 코드는 여러 삼항 연산자가 중첩되어 사용되어서, 정확하게 어떤 조건으로 값이 계산되는지 한눈에 파악하기 어려워요. +这段代码使用了多个嵌套的三元运算符,很难一眼看出计算值使用了哪个条件。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음과 같이 조건을 `if` 문으로 풀어서 사용하면 보다 명확하고 간단하게 조건을 드러낼 수 있어요. +如下使用 `if` 语句展开条件,可以简单明了地显示条件。 ```typescript const status = (() => { - if (A조건 && B조건) return "BOTH"; - if (A조건) return "A"; - if (B조건) return "B"; + if (条件A && 条件B) return "BOTH"; + if (条件A) return "A"; + if (条件B) return "B"; return "NONE"; })(); ``` diff --git a/ch/code/examples/use-bottom-sheet.md b/ch/code/examples/use-bottom-sheet.md index 931132c7..c3b8ea86 100644 --- a/ch/code/examples/use-bottom-sheet.md +++ b/ch/code/examples/use-bottom-sheet.md @@ -1,20 +1,20 @@ -# 중복 코드 허용하기 +# 允许重复代码
- +
-개발자로서 여러 페이지나 컴포넌트에 걸친 중복 코드를 하나의 Hook이나 컴포넌트로 공통화하는 경우가 많아요. -중복 코드를 하나의 컴포넌트나 Hook으로 공통화하면, 좋은 코드의 특징 중 하나인 응집도를 챙겨서, 함께 수정되어야 할 코드들을 한꺼번에 수정할 수 있어요. +作为开发者,我们经常会把跨多个页面或组件的重复代码整合于一个 Hook 或组件中来实现代码的共用。 +通过将重复代码整合到一个组件或 Hook 中,使得代码具有高内聚性(好代码的特征之一),从而能够同时修改那些需要一起修改的代码。 -그렇지만, 불필요한 결합도가 생겨서, 공통 컴포넌트나 Hook을 수정함에 따라 영향을 받는 코드의 범위가 넓어져서, 오히려 수정이 어려워질 수도 있어요. +然而,这也可能引发不必要地耦合性,导致在修改公共组件或 Hook 时,受影响的代码范围扩大,修改代码反而变得更加困难。 -처음에는 비슷하게 동작한다고 생각해서 공통화한 코드가, 이후 페이지마다 다른 특이한 요구사항이 생겨서, 점점 복잡해질 수 있어요. -동시에 공통 코드를 수정할 때마다, 그 코드에 의존하는 코드들을 일일이 제대로 테스트해야 해서, 오히려 코드 수정이 어려워지기도 하죠. +起初因功能相似而合并的公共代码,后来可能会根据各页面的特殊需求而逐渐变得复杂起来。 +而且,每次修改公共代码时,都必须逐一测试对其依赖的代码,这反而会让代码修改更加困难。 -## 📝 코드 예시 +## 📝 代码示例 -아래와 같이 점검 정보를 인자로 받아서, 점검 중이라면 점검 바텀시트를 열고, 사용자가 알림 받기에 동의하면 이를 로깅하고, 현재 화면을 닫는 Hook을 살펴볼게요. +下面有一个 Hook,它接受维修信息作为参数,如果系统当前处于维修中状态,就会打开底部动作条(bottom sheet)。如果用户同意接收通知,则会进行日志记录,并随后关闭当前屏幕。 ```typescript export const useOpenMaintenanceBottomSheet = () => { @@ -22,36 +22,36 @@ export const useOpenMaintenanceBottomSheet = () => { const logger = useLogger(); return async (maintainingInfo: TelecomMaintenanceInfo) => { - logger.log("점검 바텀시트 열림"); + logger.log("打开维修底部动作条"); const result = await maintenanceBottomSheet.open(maintainingInfo); if (result) { - logger.log("점검 바텀시트 알림받기 클릭"); + logger.log("点击维修底部动作条上的同意接收通知"); } closeView(); }; }; ``` -이 코드는 여러 페이지에서 반복적으로 사용되었기에 공통 Hook으로 분리되었어요. +这段代码因在多个页面中反复使用,被提取为一个公共 Hook。 -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 결합도 +### 耦合性 -이 Hook은 여러 페이지에서 반복적으로 보이는 로직이기에 공통화되었어요. 그렇지만 앞으로 생길 수 있는 다양한 코드 변경의 가능성을 생각해볼 수 있어요. +这个 Hook 之所以被通用化,是因为其包含了很多页面共同反复用到的逻辑。不过,在享受其带来的便利时,需要留意未来可能出现的各种代码变更的情况。 -- 만약에 페이지마다 로깅하는 값이 달라진다면? -- 만약에 어떤 페이지에서는 점검 바텀시트를 닫더라도 화면을 닫을 필요가 없다면? -- 바텀시트에서 보여지는 텍스트나 이미지를 다르게 해야 한다면? +- 每个页面需要日志记录的值不同时? +- 某些页面即使在关闭维修底部动作条时也不需要关闭整个屏幕时? +- 需要在底部动作条中显示不同文本或图像时? -위 Hook은 이런 코드 변경사항에 유연하게 대응하기 위해서 복잡하게 인자를 받아야 할 거예요. -이 Hook의 구현을 수정할 때마다, 이 Hook을 쓰는 모든 페이지들이 잘 작동하는지 테스트도 해야 할 것이고요. +上述 Hook 为了灵活对应代码变更需求,可能需要接受一些复杂的参数。 +而且,每次修改这个 Hook 时,都需要测试所有使用该 Hook 的页面,以确保动作的正常运行。 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다소 반복되어 보이는 코드일지 몰라도, 중복 코드를 허용하는 것이 좋은 방향일 수 있어요. +即便重复的代码看上去有些冗余,有时候接受这种重复代码也不失为一种好方法。 -함께 일하는 동료들과 적극적으로 소통하며 점검 바텀시트의 동작을 정확하게 이해해야 해요. -페이지에서 로깅하는 값이 같고, 점검 바텀시트의 동작이 동일하고, 바텀시트의 모양이 동일하다면, 그리고 앞으로도 그럴 예정이라면, 공통화를 통해 코드의 응집도를 높일 수 있어요. +需要积极与同事沟通,准确理解维修底部动作条的动作原理。 +如果页面上日志记录的值相同,维修底部动作条的动作一致,且其外表相同,并且预计将来也会保持现状,那么我们可以通过通用化提高代码的内聚性。 -그렇지만 페이지마다 동작이 달라질 여지가 있다면, 공통화 없이 중복 코드를 허용하는 것이 더 좋은 선택이에요. +但是,如果每个页面的行为都可能有所不同,那么不追求通用化,而是允许代码重复,可能是更好的选择。 From 0472f252a8d0ac2204f4bab17c5f21c7d6c23de9 Mon Sep 17 00:00:00 2001 From: owonie Date: Mon, 27 Jan 2025 20:41:50 +0900 Subject: [PATCH 08/16] refactor: substitute with suitable terms --- .vitepress/ch.mts | 2 +- ch/code/examples/submit-button.md | 8 ++++---- ch/code/index.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 2af3e714..5d1ab4a2 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -80,7 +80,7 @@ function sidebar(): DefaultTheme.Sidebar { text: "减少语境", items: [ { - text: "A. 拆分不同语境下的代码", + text: "A. 分离不一起运行的代码", link: "/ch/code/examples/submit-button" }, { diff --git a/ch/code/examples/submit-button.md b/ch/code/examples/submit-button.md index 6714ee2a..5b2324ad 100644 --- a/ch/code/examples/submit-button.md +++ b/ch/code/examples/submit-button.md @@ -1,10 +1,10 @@ -# 拆分不同语境下的代码 +# 分离不一起运行的代码
-如果非同步执行的代码被放在同一个函数或组件中,就很难一眼看清他们各自的作用。 +如果不同时运行的代码被放在同一个函数或组件中,就很难一眼看清他们各自的作用。 由于实现过程中充满了复杂的分支,使得很难理解每个部分的作用。 ## 📝 代码示例 @@ -40,8 +40,8 @@ function SubmitButton() { `` 组件同时处理用户可能具有的两种权限状态,且该两种状态都在同一组件中进行处理。 所以代码阅读者需要考虑的语境过多。 -例如,在下面的代码中,蓝色部分表示当用户具有仅查看权限(`'viewer'`)时执行的代码,红色部分表示当用户是普通用户时执行的代码。 -由于不同时执行的代码交织在一起,这给理解代码带来了负担。 +例如,在下面的代码中,蓝色部分表示当用户具有仅查看权限(`'viewer'`)时运行的代码,红色部分表示当用户是普通用户时运行的代码。 +由于不同时运行的代码交织在一起,这给理解代码带来了负担。 ![](../../images/examples/submit-button.png) diff --git a/ch/code/index.md b/ch/code/index.md index cefc55f5..6e186fa5 100644 --- a/ch/code/index.md +++ b/ch/code/index.md @@ -18,7 +18,7 @@ comments: false ### 提高可读性的策略 - **减少语境** - - [拆分不同语境下的代码](./examples/submit-button.md) + - [分离不一起运行的代码](./examples/submit-button.md) - [抽象实现细节](./examples/login-start-page.md) - [根据逻辑类型拆分合并的函数](./examples/use-page-state-readability.md) - **命名** From 681dbe2bfd2029769516b6f2593e5cd98b71f822 Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 00:59:15 +0900 Subject: [PATCH 09/16] docs: translate into simplified chinese --- ch/code/examples/use-page-state-coupling.md | 28 +++--- .../examples/use-page-state-readability.md | 34 +++---- ch/code/examples/use-user.md | 94 +++++++++---------- ch/code/examples/user-policy.md | 44 ++++----- 4 files changed, 100 insertions(+), 100 deletions(-) diff --git a/ch/code/examples/use-page-state-coupling.md b/ch/code/examples/use-page-state-coupling.md index 17b8cf15..b25b24ac 100644 --- a/ch/code/examples/use-page-state-coupling.md +++ b/ch/code/examples/use-page-state-coupling.md @@ -1,14 +1,14 @@ -# 책임을 하나씩 관리하기 +# 单独管理责任
- +
-쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 나누지 마세요. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 돼요. +不要根据逻辑类型(如查询参数、状态、API 调用等)为基准拆分函数。 由于同时涉及多种上下文类型,代码变得即难以理解又难以维护。 -## 📝 코드 예시 +## 📝 代码示例 -다음 `usePageState()` Hook은 페이지 전체의 URL 쿼리 파라미터를 한 번에 관리해요. +以下 `usePageState()` Hook 可以一次性管理整个页面的 URL 查询参数。 ```typescript import moment, { Moment } from "moment"; @@ -59,23 +59,23 @@ export function usePageState() { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 결합도 +### 耦合性 -이 Hook은 "이 페이지에 필요한 모든 쿼리 매개변수를 관리하는 것"이라는 광범위한 책임을 가지고 있어요. 이로 인해 페이지 내의 컴포넌트나 다른 훅들이 이 훅에 의존하게 될 수 있으며, 코드 수정을 할 때 영향 범위가 급격히 확장될 수 있어요. +该 Hook 肩负着“管理此页面所需的所有查询参数”的广泛责任。因此,页面内的组件或其他 Hook 可能会依赖于该 Hook,在修改代码时,影响范围也会急剧扩大。 -시간이 지나며 이 Hook은 유지 관리가 점점 어려워지고, 수정하기 힘든 코드로 발전할 수 있어요. +随着时间的推移,这个 Hook 会变得难以维护,最终演变为难以修改的代码。 ::: info -이 Hook은 [가독성](./use-page-state-readability.md) 관점으로도 볼 수 있어요. +这个 Hook 也可以从 [可读性](./use-page-state-readability.md) 的角度来考虑。 ::: -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음 코드와 같이 각각의 쿼리 파라미터별로 별도의 Hook을 작성할 수 있어요. +可以像下列代码一样,将每个查询参数分别编写单独的 Hook。 ```typescript import { useQueryParam } from "use-query-params"; @@ -91,5 +91,5 @@ export function useCardIdQueryParam() { } ``` -Hook이 담당하는 책임을 분리했기 때문에, 수정에 따른 영향이 갈 범위를 좁힐 수 있어요. -그래서 Hook을 수정했을 때 예상하지 못한 영향이 생기는 것을 막을 수 있어요. +由于分离了 Hook 所承担的责任,能够减少修改所带来的影响范围。 +因此,在修改 Hook 时, 能够防止产生预料之外的影响。 diff --git a/ch/code/examples/use-page-state-readability.md b/ch/code/examples/use-page-state-readability.md index fec05985..afbaa9aa 100644 --- a/ch/code/examples/use-page-state-readability.md +++ b/ch/code/examples/use-page-state-readability.md @@ -1,14 +1,14 @@ -# 로직 종류에 따라 합쳐진 함수 쪼개기 +# 根据逻辑类型拆分合并的函数
- +
-쿼리 파라미터, 상태, API 호출과 같은 로직의 종류에 따라서 함수나 컴포넌트, Hook을 만들지 마세요. 한 번에 다루는 맥락의 종류가 많아져서 이해하기 힘들고 수정하기 어려운 코드가 돼요. +不要根据逻辑类型(如查询参数、状态、API 调用等)为基准创建函数。 由于同时涉及多种上下文类型,代码变得即难以理解又难以维护。 -## 📝 코드 예시 +## 📝 代码示例 -다음 `usePageState()` Hook은 페이지 전체의 URL 쿼리 파라미터를 한 번에 관리해요. +以下 `usePageState()` Hook 可以一次性管理整个页面的 URL 查询参数。 ```typescript import moment, { Moment } from "moment"; @@ -59,29 +59,29 @@ export function usePageState() { } ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -이 Hook이 가지고 있는 책임이 "페이지가 필요한 모든 쿼리 파라미터를 관리하는 것" 임을 고려했을 때, 이 Hook이 담당할 책임이 무제한적으로 늘어날 가능성이 있어요. 새로운 쿼리 파라미터가 추가되면, 무의식적으로 이 Hook이 관리하게 되죠. +考虑到该 Hook 的责任是“管理页面所需的所有查询参数”,这个 Hook 所承担的责任有可能会无限增大。每当有新的查询参数被加入时,这个 Hook 就会自然而然地承担起管理的责任。 -점점 Hook이 담당하고 있는 영역이 넓어지면서, 구현이 길어지고, 어떤 역할을 하는 Hook인지 파악하기 힘들어져요. +随着 Hook 的责任范围逐渐扩大,实现部分也会变得越来越冗长,导致很难理解它到底扮演着什么角色。 -### 성능 +### 性能 -이 Hook을 쓰는 컴포넌트는, 이 Hook이 관리하는 어떤 쿼리 파라미터가 수정되더라도 리렌더링이 발생해요. 예를 들어서, 한 컴포넌트에서 `cardId`만 참고해도, `dateFrom`이나 `dateTo`가 변경되면 리렌더링되는 거죠. +使用该 Hook 的组件会在任何查询参数变更时重新渲染。即使一个组件只引用 `cardId`,如果 `dateFrom` 或 `dateTo` 发生变化,它也会重新渲染。 -좋은 성능을 위해서는 특정한 상태 값이 업데이트되었을 때 최소한의 부분이 리렌더링되도록 설계해야 해요. +为了提高性能,应该设计系统来确保特定状态值更新时,重新渲染的部分最小。 ::: info -이 Hook은 [결합도](./use-page-state-coupling.md) 관점으로도 볼 수 있어요. +这个 Hook 也可以从 [耦合性](./use-page-state-coupling.md) 的角度来考虑。 ::: -## ✏️ 개선해보기 +## ✏️ 尝试改善 -다음 코드와 같이 각각의 쿼리 파라미터별로 별도의 Hook을 작성할 수 있어요. +可以像下列代码一样,将每个查询参数分别编写单独的 Hook。 ```typescript import { useQueryParam } from "use-query-params"; @@ -97,5 +97,5 @@ export function useCardIdQueryParam() { } ``` -Hook이 담당하는 책임을 분리했기 때문에, 기존 `usePageState()` Hook보다 명확한 이름을 가져요. -또한 Hook을 수정했을 때 영향이 갈 범위를 좁혀서, 예상하지 못한 변경이 생기는 것을 막을 수 있어요. +在分离了 Hook 的职责后,它比原本 `usePageState()` Hook 有着更明确的名称。 +同时,通过缩小修改 Hook 时产生的影响范围,能够防止产生预料之外的变化。 diff --git a/ch/code/examples/use-user.md b/ch/code/examples/use-user.md index e42e29bb..6de3ae13 100644 --- a/ch/code/examples/use-user.md +++ b/ch/code/examples/use-user.md @@ -1,24 +1,24 @@ -# 같은 종류의 함수는 반환 타입 통일하기 +# 统一同类函数的返回类型
- +
-API 호출과 관련된 Hook들처럼 같은 종류의 함수나 Hook이 서로 다른 반환 타입을 가지면 코드의 일관성이 떨어져서, 같이 일하는 동료들이 코드를 읽는 데에 헷갈릴 수 있어요. +就像与 API 调用相关的 Hook 一样,如果同类函数或 Hook 具有不同的返回类型,代码的一致性就会降低,同事阅读代码时会产生混乱。 -## 📝 코드 예시 1: useUser +## 📝 代码示例 1: useUser -다음 `useUser` 와 `useServerTime` Hook은 모두 API 호출과 관련된 Hook이에요. +以下 `useUser` 和 `useServerTime` Hook 都与 API 调用相关。 -그렇지만 `useUser`는 `@tanstack/react-query`의 `Query` 객체를 반환하고, `useServerTime`은 서버 시간을 가져와서 데이터만 반환해요. +但是 `useUser` 返回的是 `@tanstack/react-query` 的 `Query` 对象,而 `useServerTime` 则是获取服务器时间后仅返回数据本身 。 ```typescript 9,18 -import { useQuery } from '@tanstack/react-query'; +import { useQuery } from "@tanstack/react-query"; function useUser() { const query = useQuery({ - queryKey: ['user'], - queryFn: fetchUser, + queryKey: ["user"], + queryFn: fetchUser }); return query; @@ -26,33 +26,33 @@ function useUser() { function useServerTime() { const query = useQuery({ - queryKey: ['serverTime'], - queryFn: fetchServerTime, + queryKey: ["serverTime"], + queryFn: fetchServerTime }); return query.data; } ``` -### 👃 코드 냄새 맡아보기 +### 👃 闻代码 -#### 예측 가능성 +#### 可预测性 -서버 API를 호출하는 Hook의 반환 타입이 서로 다르다면, 동료들은 이런 Hook을 쓸 때마다 반환 타입이 무엇인지 확인해야 해요. `Query` 객체를 반환한다면 `data`를 꺼내야 하고, 데이터만 반환한다면 그대로 값을 사용할 수 있죠. +如果调用服务器 API 的 Hook 返回类型各不相同,则其他同事每次使用这些 Hook 时都需要查看并确认返回类型。如果返回的是 `Query` 对象,那么就需要从中提取出 `data`;如果仅返回数据本身,则可以直接使用返回值。 -같은 종류의 동작을 하는 코드가 일관적인 규칙에 따르고 있지 않으면 코드를 읽고 쓰는 데 헷갈려요. +如果执行同一功能的代码不遵循一贯原则,则阅读和编写代码时会产生混乱。 -### ✏️ 개선해보기 +### ✏️ 尝试改善 -다음 코드처럼 서버 API를 호출하는 Hook은 일관적으로 `Query` 객체를 반환하게 하면, 팀원들이 코드에 대한 예측 가능성을 높일 수 있어요. +像下列代码一样,将调用服务器 API 的 Hook 一致地返回 `Query` 对象,可以提高团队成员对代码的可预测性。 ```typescript 9,18 -import { useQuery } from '@tanstack/react-query'; +import { useQuery } from "@tanstack/react-query"; function useUser() { const query = useQuery({ - queryKey: ['user'], - queryFn: fetchUser, + queryKey: ["user"], + queryFn: fetchUser }); return query; @@ -60,46 +60,46 @@ function useUser() { function useServerTime() { const query = useQuery({ - queryKey: ['serverTime'], - queryFn: fetchServerTime, + queryKey: ["serverTime"], + queryFn: fetchServerTime }); return query; } ``` -## 📝 코드 예시 2: checkIsValid +## 📝 代码示例 2: checkIsValid -다음 `checkIsNameValid` 와 `checkIsAgeValid`는 모두 이름과 나이가 올바른지 검증하는 함수예요. +下面 `checkIsNameValid` 和 `checkIsAgeValid` 都是用来检验姓名和年龄的有效性的函数。 ```typescript -/** 사용자 이름은 20자 미만이어야 해요. */ +/** 用户名必须少于20个字符。 */ function checkIsNameValid(name: string) { const isValid = name.length > 0 && name.length < 20; return isValid; } -/** 사용자 나이는 18세 이상 99세 이하의 자연수여야 해요. */ +/** 用户的年龄必须是18岁至99岁之间的自然数。 */ function checkIsAgeValid(age: number) { if (!Number.isInteger(age)) { return { ok: false, - reason: "나이는 정수여야 해요." + reason: "年龄必须是整数。" }; } if (age < 18) { return { ok: false, - reason: "나이는 18세 이상이어야 해요." + reason: "年龄必须年满18岁。" }; } if (age > 99) { return { ok: false, - reason: "나이는 99세 이하이어야 해요." + reason: "年龄必须在99岁以下。" }; } @@ -107,71 +107,71 @@ function checkIsAgeValid(age: number) { } ``` -### 👃 코드 냄새 맡아보기 +### 👃 闻代码 -#### 예측 가능성 +#### 可预测性 -유효성 검사 함수의 반환 값이 다르다면, 동료들은 함수를 쓸 때마다 반환 타입을 확인해야 해서 혼란이 생겨요. +如果有效性检查函数的返回值各不相同,则同事们在使用这些函数时每次都需要确认返回类型,这很容易造成混淆。 -특히 [엄격한 불리언 검증](https://typescript-eslint.io/rules/strict-boolean-expressions/)과 같은 기능을 사용하지 않는 경우, 코드의 오류가 생기는 원인이 될 수 있어요. +特别是在不使用诸如 [严格布尔表达式](https://typescript-eslint.io/rules/strict-boolean-expressions/) 类似功能的情况下,这很可能成为代码中出错的源头。 ```typescript -// 이 코드는 이름이 규칙에 맞는지 올바르게 검증해요 +// 这段代码验证姓名是否符合规则 if (checkIsNameValid(name)) { // ... } -// 이 함수는 항상 객체 { ok, ... } 를 반환하기 때문에, -// `if` 문 안에 있는 코드가 항상 실행돼요 +// 该函数只返回一个对象 { ok, ... }, +// `if` 语句内的代码总是会被执行 if (checkIsAgeValid(age)) { // ... } ``` -### ✏️ 개선해보기 +### ✏️ 尝试改善 -다음 코드처럼 유효성 검사 함수가 일관적으로 `{ ok, ... }` 타입의 객체를 반환하게 할 수 있어요. +像下列代码一样,可将有效性检验函数始终返回一个 `{ ok, ... }` 类型的对象。 ```typescript -/** 사용자 이름은 20자 미만이어야 해요. */ +/** 用户名必须少于20个字符。 */ function checkIsNameValid(name: string) { if (name.length === 0) { return { ok: false, - reason: "이름은 빈 값일 수 없어요." + reason: "姓名不允许为空值。" }; - } - + } + if (name.length >= 20) { return { ok: false, - reason: '이름은 20자 이상 입력할 수 없어요.', + reason: "姓名不能超过20个字符。" }; } return { ok: true }; } -/** 사용자 나이는 18세 이상 99세 이하의 자연수여야 해요. */ +/** 用户的年龄必须是18岁至99岁之间的自然数。 */ function checkIsAgeValid(age: number) { if (!Number.isInteger(age)) { return { ok: false, - reason: "나이는 정수여야 해요." + reason: "年龄必须是整数。" }; } if (age < 18) { return { ok: false, - reason: "나이는 18세 이상이어야 해요." + reason: "年龄必须年满18岁。" }; } if (age > 99) { return { ok: false, - reason: "나이는 99세 이하이어야 해요." + reason: "年龄必须在99岁以下。" }; } diff --git a/ch/code/examples/user-policy.md b/ch/code/examples/user-policy.md index 578535e9..3c1ee175 100644 --- a/ch/code/examples/user-policy.md +++ b/ch/code/examples/user-policy.md @@ -1,20 +1,20 @@ -# 시점 이동 줄이기 +# 减少视点转换
- +
-코드를 읽을 때 코드의 위아래를 왔다갔다 하면서 읽거나, 여러 파일이나 함수, 변수를 넘나들면서 읽는 것을 **시점 이동**이라고 해요. -시점이 여러 번 이동할수록 코드를 파악하는 데에 시간이 더 걸리고, 맥락을 파악하는 데에 어려움이 있을 수 있어요. +在阅读代码时,反复浏览代码的各个部分,或者在多个文件、函数、变量之间翻看阅读的现象被称为 **视点转移**。 +随着视点的多次转移,需要理解代码的时间也随之增加,很难把握代码的整体语境。 -코드를 위에서 아래로, 하나의 함수나 파일에서 읽을 수 있도록 코드를 작성하면, 읽는 사람이 동작을 빠르게 파악할 수 있게 돼요. +如果将代码编写成从上到下、在一个函数或文件中就能顺序阅读的方式,代码阅读者可以迅速理解其功能。 -## 📝 코드 예시 +## 📝 代码示例 -다음 코드에서는 사용자의 권한에 따라서 버튼을 다르게 보여줘요. +下列代码会根据用户的权限显示不同的按钮。 -- 사용자의 권한이 관리자(Admin)라면, `Invite`와 `View` 버튼을 보여줘요. -- 사용자의 권한이 보기 전용(Viewer)라면, `Invite` 버튼은 비활성화하고, `View` 버튼을 보여줘요. +- 如果用户权限是管理员(Admin),则显示 `Invite` 和 `View` 按钮。 +- 如果用户权限是只读用户(Viewer),则非激活 `Invite` 按钮,显示 `View` 按钮。 ```tsx function Page() { @@ -44,21 +44,21 @@ const POLICY_SET = { }; ``` -## 👃 코드 냄새 맡아보기 +## 👃 闻代码 -### 가독성 +### 可读性 -이 코드에서 `Invite` 버튼이 비활성화된 이유를 이해하려고 한다면, `policy.canInvite` → `getPolicyByRole(user.role)` → `POLICY_SET` 순으로 코드를 위아래를 오가며 읽어야 해요. -이 과정에서 3번의 시점 이동이 발생해서, 코드를 읽는 사람이 맥락을 유지해 가며 읽기 어려워졌어요. +为了理解这段代码中为何 `Invite` 按钮被非激活,你需要按照 `policy.canInvite` → `getPolicyByRole(user.role)` → `POLICY_SET` 的顺序,在代码中上下翻阅进行阅读。 +在此过程中,发生了三次视点转移,使得代码阅读者很难维持上下文的语境,增加了理解的难度。 -`POLICY_SET` 같은 추상화를 사용해서 권한에 따라 버튼 상태를 관리하는 것은 권한 체계가 복잡한 경우에는 유용할 수 있지만, 지금처럼 간단할 때는 오히려 읽는 사람이 코드를 이해하기 어렵게 만들어요. +虽然使用 `POLICY_SET` 等抽象来按权限管理按钮状态在复杂权限体系中很有用,但在当前简单场景下却增加了阅读者的代码理解难度。같은 -## ✏️ 개선해보기 +## ✏️ 尝试改善 -### A. 조건을 펼쳐서 그대로 드러내기 +### A. 展开并明确展示条件 -권한에 따른 조건을 요구사항 그대로 코드에 드러내는 방법이에요. 이렇게 하면 `Invite` 버튼이 비활성화되는 때를 코드에서 바로 확인할 수 있어요. -코드를 위에서 아래로만 읽으면 한눈에 권한을 다루는 로직을 파악할 수 있어요. +直接在代码中明确展示了基于权限的条件。这样一来,代码中很容易看出 `Invite` 按钮何时会被非激活。 +只需阅读代码的上下文,就能一眼看清处理权限的逻辑。코드를 위에서 아래로만 읽으면 한눈에 권한을 다루는 로직을 파악할 수 있어요. ```tsx function Page() { @@ -85,17 +85,17 @@ function Page() { } ``` -### B. 조건을 한눈에 볼 수 있는 객체로 만들기 +### B. 将条件整理成清晰易懂的对象形式 -권한을 다루는 로직을 컴포넌트 안에서 객체로 관리해서, 여러 차례의 시점 이동 없이 한눈에 조건을 파악할 수 있게 수정할 수 있어요. -`canInvite`와 `canView`의 조건을 `Page` 컴포넌트만 보면 확인할 수 있어요. +通过以对象的形式在组件内部管理权限逻辑,可以减少不必要的视点转移,一眼看清并修改条件。 +只需查看 `Page` 组件,就能确认 `canInvite` 和 `canView` 的条件。 ```tsx function Page() { const user = useUser(); const policy = { admin: { canInvite: true, canView: true }, - viewer: { canInvite: false, canView: true }, + viewer: { canInvite: false, canView: true } }[user.role]; return ( From 57cbe82a3e77c2d073cfc19135485e28eff98ea5 Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 01:17:13 +0900 Subject: [PATCH 10/16] feat: add chSearch --- .vitepress/shared.mts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.vitepress/shared.mts b/.vitepress/shared.mts index 99bd8354..64ac7656 100644 --- a/.vitepress/shared.mts +++ b/.vitepress/shared.mts @@ -1,5 +1,6 @@ import { defineConfig, HeadConfig } from "vitepress"; import { search as koSearch } from "./ko.mts"; +import { search as chSearch } from "./ch.mts"; export const shared = defineConfig({ lastUpdated: true, @@ -30,11 +31,15 @@ export const shared = defineConfig({ transformHead: ({ pageData }) => { const head: HeadConfig[] = []; - const title = pageData.frontmatter.title || pageData.title || 'Frontend Fundamentals'; - const description = pageData.frontmatter.description || pageData.description || 'Guidelines for easily modifiable frontend code'; + const title = + pageData.frontmatter.title || pageData.title || "Frontend Fundamentals"; + const description = + pageData.frontmatter.description || + pageData.description || + "Guidelines for easily modifiable frontend code"; - head.push(['meta', { property: 'og:title', content: title }]); - head.push(['meta', { property: 'og:description', content: description }]); + head.push(["meta", { property: "og:title", content: title }]); + head.push(["meta", { property: "og:description", content: description }]); return head; }, @@ -50,7 +55,8 @@ export const shared = defineConfig({ provider: "local", options: { locales: { - ...koSearch + ...koSearch, + ...chSearch } } }, From 783d461c125a44c0d4a9afb4943f5b944f699c6e Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 01:41:29 +0900 Subject: [PATCH 11/16] fix: change chSearch option --- .vitepress/ch.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 5d1ab4a2..498b4703 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -182,7 +182,7 @@ function sidebar(): DefaultTheme.Sidebar { } export const search: DefaultTheme.LocalSearchOptions["locales"] = { - root: { + ch: { translations: { button: { buttonText: "搜索", From 74a3e2aef8652d64c72c7184081431113cdcadab Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 02:49:11 +0900 Subject: [PATCH 12/16] fix: fix typo with inspection --- .vitepress/ch.mts | 2 +- ch/code/community.md | 3 +-- ch/code/examples/condition-name.md | 12 ++++++------ ch/code/examples/http.md | 2 +- ch/code/examples/magic-number-cohesion.md | 2 +- ch/code/examples/magic-number-readability.md | 2 +- ch/code/examples/submit-button.md | 6 +++--- ch/code/examples/user-policy.md | 10 +++++----- ch/code/index.md | 10 +++++----- 9 files changed, 24 insertions(+), 25 deletions(-) diff --git a/.vitepress/ch.mts b/.vitepress/ch.mts index 498b4703..957d472d 100644 --- a/.vitepress/ch.mts +++ b/.vitepress/ch.mts @@ -112,7 +112,7 @@ function sidebar(): DefaultTheme.Sidebar { text: "使其从上到下顺利阅读", items: [ { - text: "A. 减少视点转换", + text: "A. 减少视点转移", link: "/ch/code/examples/user-policy" }, { diff --git a/ch/code/community.md b/ch/code/community.md index 2299ee81..85c1113c 100644 --- a/ch/code/community.md +++ b/ch/code/community.md @@ -25,8 +25,7 @@ comments: false ## 为好代码标准添加意见 -如果对好代码的标准有意见,或者想提出新的观点,可以参与投票选出你认为更好的代码,并留下自己的意见。 -与社区沟通,共同构建更加丰富而深入的标准。 +如果对好代码的标准有意见,或者想提出新的观点,可以参与投票选出你认为更好的代码,并留下自己的意见。与社区沟通,共同构建更加丰富而深入的标准。 这可以成为一个契机,帮助你确立判断两段代码之间哪一段更好的标准。 diff --git a/ch/code/examples/condition-name.md b/ch/code/examples/condition-name.md index 4be4ae1a..96b32c2a 100644 --- a/ch/code/examples/condition-name.md +++ b/ch/code/examples/condition-name.md @@ -49,20 +49,20 @@ const matchedProducts = products.filter((product) => { 通过明确命名筛选同类且价格范围的商品条件,可以避免追踪复杂的条件表达式,清晰表达代码的意图。 -## 🔍 深入了解: 为条件式命名的标准 +## 🔍 深入了解:为条件式命名的标准 什么时候适合给条件表达式或函数命名并将其提取? ### 适合为条件命名的情况 -- **处理复杂逻辑时**: 当条件语句或函数中的复杂逻辑跨越多行时,最好为其命名,明确展示函数的作用。这样可以提高代码可读性,维护和审查变得更加容易。 +- **处理复杂逻辑时**:当条件语句或函数中的复杂逻辑跨越多行时,最好为其命名,明确展示函数的作用。这样可以提高代码可读性,维护和审查变得更加容易。 -- **需要重用时**: 如果同一逻辑可能在多个地方反复使用,可以通过声明变量或函数来实现重用。这样可以减少代码重复,便于后续的维护。 +- **需要重用时**:如果同一逻辑可能在多个地方反复使用,可以通过声明变量或函数来实现重用。这样可以减少代码重复,便于后续的维护。 -- **需要单元测试时**: 分离函数后,可以独立编写单元测试。单元测试可以轻松验证函数是否正常工作,尤其在测试复杂逻辑时非常实用。 +- **需要单元测试时**:分离函数后,可以独立编写单元测试。单元测试可以轻松验证函数是否正常工作,尤其在测试复杂逻辑时非常实用。 ### 不需要为条件命名的情况 -- **当逻辑简单时**: 如果逻辑非常简单,实际上不需要为其命名。例如,将数组中的元素翻倍的代码`arr.map(x => x * 2)`,即使不命名,也很直观。 +- **当逻辑简单时**:如果逻辑非常简单,实际上不需要为其命名。例如,将数组中的元素翻倍的代码 `arr.map(x => x * 2)` ,即使不命名,也很直观。 -- **当只使用一次时**: 如果某个逻辑在代码中只出现一次,而且逻辑并不复杂,那么在匿名函数中直接处理逻辑可能更加直观。 +- **当只使用一次时**:如果某个逻辑在代码中只出现一次,而且逻辑并不复杂,那么在匿名函数中直接处理逻辑可能更加直观。 diff --git a/ch/code/examples/http.md b/ch/code/examples/http.md index 7d094f81..cc8e0214 100644 --- a/ch/code/examples/http.md +++ b/ch/code/examples/http.md @@ -47,7 +47,7 @@ export async function fetchUser() { ## ✏️ 尝试改善 -为了提高函数行为的可预测性,在服务中自定义函数时,应该使用与库函数明显区分开来的、具有描述行的名称。 +为了提高函数行为的可预测性,在服务中自定义函数时,应该使用与库函数明显区分开来的、具有描述性的名称。 ::: code-group diff --git a/ch/code/examples/magic-number-cohesion.md b/ch/code/examples/magic-number-cohesion.md index d66c834a..3c21dde3 100644 --- a/ch/code/examples/magic-number-cohesion.md +++ b/ch/code/examples/magic-number-cohesion.md @@ -6,7 +6,7 @@ **魔数**(Magic Number)指的是缺乏明确说明而直接插入的数值。 -例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一整天的时间。 +例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一天的时间。 ## 📝 代码示例 diff --git a/ch/code/examples/magic-number-readability.md b/ch/code/examples/magic-number-readability.md index 77413257..85cabece 100644 --- a/ch/code/examples/magic-number-readability.md +++ b/ch/code/examples/magic-number-readability.md @@ -6,7 +6,7 @@ **魔数**(Magic Number)指的是缺乏明确说明而直接插入的数值。 -例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一整天的时间。 +例如,直接使用 `404` 来表示未找到(Not Found)的 HTTP 状态码,或者直接使用 `86400` 秒来表示一天的时间。 ## 📝 代码示例 diff --git a/ch/code/examples/submit-button.md b/ch/code/examples/submit-button.md index 5b2324ad..af7e28f0 100644 --- a/ch/code/examples/submit-button.md +++ b/ch/code/examples/submit-button.md @@ -5,7 +5,7 @@
如果不同时运行的代码被放在同一个函数或组件中,就很难一眼看清他们各自的作用。 -由于实现过程中充满了复杂的分支,使得很难理解每个部分的作用。 +实现过程中内含复杂的分支,很难理解代码各个部分的作用。 ## 📝 代码示例 @@ -41,7 +41,7 @@ function SubmitButton() { 所以代码阅读者需要考虑的语境过多。 例如,在下面的代码中,蓝色部分表示当用户具有仅查看权限(`'viewer'`)时运行的代码,红色部分表示当用户是普通用户时运行的代码。 -由于不同时运行的代码交织在一起,这给理解代码带来了负担。 +由于不同时运行的代码交织在一起,理解代码时产生负担。 ![](../../images/examples/submit-button.png) @@ -70,4 +70,4 @@ function AdminSubmitButton() { ``` - 随着原本分散在 `` 代码各处的分支合并为一,分支数量减少。 -- `` 和 `` 各自值管理一个分支,所以代码阅读者需要考虑的语境减少。 +- `` 和 `` 各自仅管理一个分支,所以代码阅读者需要考虑的语境减少。 diff --git a/ch/code/examples/user-policy.md b/ch/code/examples/user-policy.md index 3c1ee175..7f465bcc 100644 --- a/ch/code/examples/user-policy.md +++ b/ch/code/examples/user-policy.md @@ -1,10 +1,10 @@ -# 减少视点转换 +# 减少视点转移
-在阅读代码时,反复浏览代码的各个部分,或者在多个文件、函数、变量之间翻看阅读的现象被称为 **视点转移**。 +在阅读代码时,反复浏览代码的各个部分,或者在多个文件、函数、变量之间翻看阅读的现象被称为**视点转移**。 随着视点的多次转移,需要理解代码的时间也随之增加,很难把握代码的整体语境。 如果将代码编写成从上到下、在一个函数或文件中就能顺序阅读的方式,代码阅读者可以迅速理解其功能。 @@ -14,7 +14,7 @@ 下列代码会根据用户的权限显示不同的按钮。 - 如果用户权限是管理员(Admin),则显示 `Invite` 和 `View` 按钮。 -- 如果用户权限是只读用户(Viewer),则非激活 `Invite` 按钮,显示 `View` 按钮。 +- 如果用户权限是仅查看用户(Viewer),则非激活 `Invite` 按钮,显示 `View` 按钮。 ```tsx function Page() { @@ -51,14 +51,14 @@ const POLICY_SET = { 为了理解这段代码中为何 `Invite` 按钮被非激活,你需要按照 `policy.canInvite` → `getPolicyByRole(user.role)` → `POLICY_SET` 的顺序,在代码中上下翻阅进行阅读。 在此过程中,发生了三次视点转移,使得代码阅读者很难维持上下文的语境,增加了理解的难度。 -虽然使用 `POLICY_SET` 等抽象来按权限管理按钮状态在复杂权限体系中很有用,但在当前简单场景下却增加了阅读者的代码理解难度。같은 +虽然使用 `POLICY_SET` 等抽象来按权限管理按钮状态在复杂权限体系中很有用,但在当前简单场景下却增加了阅读者的代码理解难度。 ## ✏️ 尝试改善 ### A. 展开并明确展示条件 直接在代码中明确展示了基于权限的条件。这样一来,代码中很容易看出 `Invite` 按钮何时会被非激活。 -只需阅读代码的上下文,就能一眼看清处理权限的逻辑。코드를 위에서 아래로만 읽으면 한눈에 권한을 다루는 로직을 파악할 수 있어요. +只需阅读代码的上下文,就能一眼看清处理权限的逻辑。 ```tsx function Page() { diff --git a/ch/code/index.md b/ch/code/index.md index 6e186fa5..1c61ec90 100644 --- a/ch/code/index.md +++ b/ch/code/index.md @@ -4,7 +4,7 @@ comments: false # 易于修改的代码 -好的前端代码是 **易于修改的** 代码。 +好的前端代码是**易于修改的**代码。 在实现新需求时,能够轻松修改和部署的代码被认为好的代码。 你可以根据四个标准判断代码是否易于修改。 @@ -25,7 +25,7 @@ comments: false - [为复杂条件命名](./examples/condition-name.md) - [为魔数命名](./examples/magic-number-readability.md) - **使其从上到下顺利阅读** - - [减少视点转换](./examples/user-policy.md) + - [减少视点转移](./examples/user-policy.md) - [简化三元运算符](./examples/ternary-operator.md) ## 2. 可预测性 @@ -37,7 +37,7 @@ comments: false - [避免命名重复](./examples/http.md) - [统一同类函数的返回类型](./examples/use-user.md) -- [解释隐藏的逻辑](./examples/hidden-logic.md) +- [揭示隐藏的逻辑](./examples/hidden-logic.md) ## 3. 内聚性 @@ -74,8 +74,8 @@ comments: false 遗憾的是,这四个标准很难同时兼顾。 -例如,通过共同化和抽象化提高代码的内聚性,可确保函数或变数总是一同修改。代码进一步抽象化后,可读性也会随之降低。 +例如,通过通用化和抽象化提高代码的内聚性,可确保函数或变数总是一同修改。然而,代码进一步抽象化后,可读性也会随之降低。 -允许代码重复可以减少代码的影响范围,从而降低耦合性。然而,这也可能导致在修改一处代码时,另一处未被及时修改,从而影响内聚性。 +允许代码重复,可以减少代码的影响范围,从而降低耦合性。然而,这也可能导致在修改一处代码时,另一处未被及时修改,从而降低内聚性。 前端开发者需要结合当前面临的具体情况,深入思考并在不同价值之间权衡取舍,以确保代码在长期内更易于维护和修改。 From 2a6cf67e0616d0eca486ded0d5d2a65ea1a7d896 Mon Sep 17 00:00:00 2001 From: Owon Date: Tue, 28 Jan 2025 18:30:26 +0900 Subject: [PATCH 13/16] fix: fix typo in hidden-logic.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同时 -> 同事 Co-authored-by: Jinyeong Seol --- ch/code/examples/hidden-logic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch/code/examples/hidden-logic.md b/ch/code/examples/hidden-logic.md index 105ff76b..c010945e 100644 --- a/ch/code/examples/hidden-logic.md +++ b/ch/code/examples/hidden-logic.md @@ -4,7 +4,7 @@ -如果函数或组件的名称、参数、返回值中存在未明确表达的隐藏逻辑,那么与你合作的同时可能会难以预测其行为。 +如果函数或组件的名称、参数、返回值中存在未明确表达的隐藏逻辑,那么与你合作的同事可能会难以预测其行为。 ## 📝 代码示例 From de838ab048926f0d8f18219fb87e7f5be90edc58 Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 18:42:55 +0900 Subject: [PATCH 14/16] feat: implement dark mode image support in markdown --- ch/code/examples/submit-button.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ch/code/examples/submit-button.md b/ch/code/examples/submit-button.md index af7e28f0..02ad2c20 100644 --- a/ch/code/examples/submit-button.md +++ b/ch/code/examples/submit-button.md @@ -43,7 +43,8 @@ function SubmitButton() { 例如,在下面的代码中,蓝色部分表示当用户具有仅查看权限(`'viewer'`)时运行的代码,红色部分表示当用户是普通用户时运行的代码。 由于不同时运行的代码交织在一起,理解代码时产生负担。 -![](../../images/examples/submit-button.png) +![](../../../images/examples/submit-button.png){.light-only} +![](../../../images/examples/submit-button-dark.png){.dark-only} ## ✏️ 尝试改善 From e9eb42737a256222bebacf37449672dd6c0a9cc3 Mon Sep 17 00:00:00 2001 From: owonie Date: Tue, 28 Jan 2025 18:55:26 +0900 Subject: [PATCH 15/16] docs: improve item-edit-modal.md by adding abstraction concepts --- ch/code/examples/item-edit-modal.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ch/code/examples/item-edit-modal.md b/ch/code/examples/item-edit-modal.md index febecba7..3a734a15 100644 --- a/ch/code/examples/item-edit-modal.md +++ b/ch/code/examples/item-edit-modal.md @@ -104,3 +104,5 @@ function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) { ); } ``` + +组合(Composition)模式不仅可以减少传递 Props 的问题,还能消除不必要的中间抽象,帮助开发者明确理解组件的角色和意图。 From 87921008b17111589b281ea379d8909543f8421a Mon Sep 17 00:00:00 2001 From: owonie Date: Wed, 29 Jan 2025 16:28:49 +0900 Subject: [PATCH 16/16] fix: correct localized link --- ch/code/examples/condition-name.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch/code/examples/condition-name.md b/ch/code/examples/condition-name.md index 96b32c2a..b31ead6a 100644 --- a/ch/code/examples/condition-name.md +++ b/ch/code/examples/condition-name.md @@ -28,7 +28,7 @@ const result = products.filter((product) => 代码阅读者需要考虑的上下文过多,导致可读性变差。[^1] -[^1]: [程序员超强大脑](https://www.yes24.com/product/goods/105911017)一书中提到,人的大脑一次性能够处理和存储的信息大约是 6 个。 +[^1]: [程序员超强大脑](https://product.dangdang.com/29567786.html)一书中提到,人的大脑一次性能够处理和存储的信息大约是 6 个。 ## ✏️ 尝试改善