diff --git a/README.md b/README.md
index cae487b..4bfd035 100644
--- a/README.md
+++ b/README.md
@@ -3,43 +3,65 @@
[](https://travis-ci.com/zedr/clean-code-python)
[](https://www.python.org/download/releases/3.8.3/)
-## Table of Contents
-1. [Introduction](#introduction)
-2. [Variables](#variables)
-3. [Functions](#functions)
-5. [Classes](#classes)
- 1. [S: Single Responsibility Principle (SRP)](#single-responsibility-principle-srp)
- 2. [O: Open/Closed Principle (OCP)](#openclosed-principle-ocp)
- 3. [L: Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp)
- 4. [I: Interface Segregation Principle (ISP)](#interface-segregation-principle-isp)
- 5. [D: Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip)
-6. [Don't repeat yourself (DRY)](#dont-repeat-yourself-dry)
-7. [Translations](#translations)
+
-## Introduction
+## Author
-Software engineering principles, from Robert C. Martin's book
-[*Clean
-Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)
-, adapted for Python. This is not a style guide. It's a guide to producing
-readable, reusable, and refactorable software in Python.
+[@zedr](https://github.com/zedr), Thank you for making a great document!!!
-Not every principle herein has to be strictly followed, and even fewer will be
-universally agreed upon. These are guidelines and nothing more, but they are
-ones codified over many years of collective experience by the authors of *Clean
-Code*.
+
-Adapted
-from [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)
+## 목차
-Targets Python3.7+
+1. [소개](#소개)
+2. [변수](#변수)
+3. [함수](#함수)
+4. [클래스 (객체지향 5원칙)](#클래스)
+ 1. [S: 단일 책임 원칙 (Single Responsibility Principle; SRP)](#단일-책임-원칙-single-responsibility-principle-srp)
+ 2. [O: 개방/폐쇄 원칙 (Open/Closed Principle; OCP)](#개방폐쇄-원칙-openclosed-principle-ocp)
+ 3. [L: 리스코프 치환 원칙 (Liskov Substitution Principle; LSP)](#리스코프-치환-원칙-liskov-substitution-principle-lsp)
+ 4. [I: 인터페이스 분리 원칙 (Interface Segregation Principle; ISP)](#인터페이스-분리-원칙-interface-segregation-principle-isp)
+ 5. [D: 의존성 역전 원칙 (Dependency Inversion Principle; DIP)](#의존성-역전-원칙-dependency-inversion-principle-dip)
+5. [반복은 지양합시다. (Don't repeat yourself; DRY)](#반복은-지양합시다-dont-repeat-yourself-dry)
+6. [Translations](#translations)
-## **Variables**
+
-### Use meaningful and pronounceable variable names
+## 소개
-**Bad:**
+Robert C. Martin의 책, [*Clean
+Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)을 참고하였습니다.
+
+
+
+이 문서는 Python에 맞게 수정되었으며 style guide가 아닙니다.
+
+이 문서는 Python으로 읽을 수 있고(readable) 재사용 가능하며(reusable), 리펙토링 가능한(refactorable) 소프트웨어를 만들어 내기 위한 가이드라인을 제시합니다.
+
+
+
+이 문서의 모든 것을 완전히 따를 필요는 없으며, 각 구성원 간 보편적 합의에 따라가면 됩니다.
+
+다시 말하지만 이 문서에서 언급하는 것들은 모두 지침일 뿐입니다.
+다만, *Clean Code*의 저자들에 의해 수년간의 경험에 의해 정립된 것들입니다.
+
+
+
+[clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)의 문서를 `Python 3.7+` 버전에 맞게 수정하였습니다.
+
+
+
+
+## **변수**
+
+### 변수 이름은 의미가 있어야 하며, 발음할 수 있어야 합니다.
+(meaningful, pronounceable)
+
+
+
+
+**나쁜 예:**
```python
import datetime
@@ -47,10 +69,9 @@ import datetime
ymdstr = datetime.date.today().strftime("%y-%m-%d")
```
-Additionally, there's no need to add the type of the variable (str) to its
-name.
+
-**Good**:
+**좋은 예:**
```python
import datetime
@@ -58,12 +79,16 @@ import datetime
current_date: str = datetime.date.today().strftime("%y-%m-%d")
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
+
+### 동일 대상의 변수에 대해서는 동일 어휘를 사용합시다.
-### Use the same vocabulary for the same type of variable
+
-**Bad:**
-Here we use three different names for the same underlying entity:
+**나쁜 예:**
+아래 예제는 동일 대상(entity)에 대해 3개의 다른 이름을 사용합니다.
```python
def get_user_info(): pass
@@ -75,9 +100,10 @@ def get_client_data(): pass
def get_customer_record(): pass
```
-**Good**:
-If the entity is the same, you should be consistent in referring to it in your
-functions:
+
+
+**좋은 예:**
+만약 entity가 동일하다면, 일관성 있게(consistent) 변수나 함수의 이름을 짓는 것이 좋습니다.
```python
def get_user_info(): pass
@@ -89,10 +115,10 @@ def get_user_data(): pass
def get_user_record(): pass
```
-**Even better**
-Python is (also) an object oriented programming language. If it makes sense,
-package the functions together with the concrete implementation of the entity
-in your code, as instance attributes, property methods, or methods:
+
+
+**참고하면 좋은 예:**
+Python은 객체 지향 프로그래밍 언어입니다. 필요한 경우 인스턴스의 속성(attribute), 프로퍼티 메소드(property method)나 메소드(method)와 함께 코드에서 entity의 구체적인 구현 및 패키지화하는 것이 좋습니다.
```python
from typing import Union, Dict
@@ -113,16 +139,21 @@ class User:
return Record()
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
-### Use searchable names
+
-We will read more code than we will ever write. It's important that the code we
-do write is readable and searchable. By *not* naming variables that end up
-being meaningful for understanding our program, we hurt our readers. Make your
-names searchable.
+### 검색에 용이한 이름을 사용합시다.
+우리는 코딩을 하며 많은 코드를 읽습니다. 때문에 우리가 작성하는 코드를 읽기 쉽고 검색 가능한 이름으로 선언하는 것은 중요합니다.
-**Bad:**
+만약 변수를 선언할 때 의미가 없거나 검색에 어려움을 주는 이름으로 선언한다면, 우리의 코드를 읽는 다른 사람들이 힘들어할 것입니다.
+
+검색 가능한(유추 가능한) 이름을 사용합시다.
+
+
+
+**나쁜 예:**
```python
import time
@@ -131,7 +162,9 @@ import time
time.sleep(86400)
```
-**Good**:
+
+
+**좋은 예:**
```python
import time
@@ -141,11 +174,16 @@ SECONDS_IN_A_DAY = 60 * 60 * 24
time.sleep(SECONDS_IN_A_DAY)
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
+
+### 변수는 독립적이어야 합니다.
+(explanatory)
-### Use explanatory variables
+
-**Bad:**
+**나쁜 예:**
```python
import re
@@ -158,25 +196,29 @@ if matches:
print(f"{matches[1]}: {matches[2]}")
```
-**Not bad**:
+
-It's better, but we are still heavily dependent on regex.
+**나쁘지는 않은 예:**
+
+나쁘지는 않지만, 여전히 regex의 결과에 의존하고 있습니다.
```python
import re
address = "One Infinite Loop, Cupertino 95014"
city_zip_code_regex = r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$"
-matches = re.match(city_zip_code_regex, address)
+matches = re.match(city_zip_code_regex, address)
if matches:
city, zip_code = matches.groups()
print(f"{city}: {zip_code}")
```
-**Good**:
+
+
+**좋은 예:**
-Decrease dependence on regex by naming subpatterns.
+하위 패턴의 이름을 지정함으로써 regex 결과에 대한 의존성을 줄일 수 있습니다.
```python
import re
@@ -189,14 +231,18 @@ if matches:
print(f"{matches['city']}, {matches['zip_code']}")
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
-### Avoid Mental Mapping
+
-Don’t force the reader of your code to translate what the variable means.
-Explicit is better than implicit.
+### 읽는 사람으로 하여금 기능을 유추하도록 만드는 이름을 짓지 마세요.
+변수가 의미하는 바가 무엇인지를 코드를 상세히 보지 않아도 알 수 있도록 하세요.
-**Bad:**
+명시적인 것이 암묵적인 것보다 좋습니다.
+
+
+
+**나쁜 예:**
```python
seq = ("Austin", "New York", "San Francisco")
@@ -209,7 +255,9 @@ for item in seq:
print(item)
```
-**Good**:
+
+
+**좋은 예:**
```python
locations = ("Austin", "New York", "San Francisco")
@@ -221,14 +269,17 @@ for location in locations:
print(location)
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
-### Don't add unneeded context
+
-If your class/object name tells you something, don't repeat that in your
-variable name.
+### 불필요한 context는 추가하지 마세요.
-**Bad:**
+클래스/객체 이름이 무언가를 이미 알려주는 경우, 변수 이름에서 이를 반복하지 마세요.
+
+
+
+**나쁜 예:**
```python
class Car:
@@ -237,7 +288,9 @@ class Car:
car_color: str
```
-**Good**:
+
+
+**좋은 예**:
```python
class Car:
@@ -246,13 +299,17 @@ class Car:
color: str
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
-### Use default arguments instead of short circuiting or conditionals
+
-**Tricky**
+### short circuiting 또는 conditionals 대신 default parameter를 사용하세요.
-Why write:
+여기서 `short circuiting`은 논리 연산(and, or)를 의미합니다.
+
+
+
+**다음과 같은 상황에서**:
```python
import hashlib
@@ -264,10 +321,15 @@ def create_micro_brewery(name):
# etc.
```
-... when you can specify a default argument instead? This also makes it clear
-that you are expecting a string as the argument.
+
+
+만약 위와 같이 조건문을 사용하는 것 대신 매개변수만을 사용하더라도 함수의 동작에 아무런 영향이 없다는 것을 알 수 있습니다.
-**Good**:
+우리는 위 코드를 아래와 같이 수정하고 싶을 것입니다.
+
+
+
+**좋은 예**:
```python
import hashlib
@@ -278,19 +340,27 @@ def create_micro_brewery(name: str = "Hipster Brew Co."):
# etc.
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
+
+## **함수**
+
+### 함수는 작업의 단위입니다.
+
+함수는 소프트웨어 엔지니어링에서 가장 중요한 rule 중 하나입니다.
-## **Functions**
+함수들이 하나 이상의 작업을 수행한다면 관리, 테스트 및 추론에 어려움을 겪을 것입니다.
-### Functions should do one thing
+
-This is by far the most important rule in software engineering. When functions
-do more than one thing, they are harder to compose, test, and reason about.
-When you can isolate a function to just one action, they can be refactored
-easily and your code will read much cleaner. If you take nothing else away from
-this guide other than this, you'll be ahead of many developers.
+함수를 하나의 작업으로 분리한다면, 리펙토링(refactoring)이 쉬워지고 코드를 훨씬 깨끗하게 만들 수 있습니다.
-**Bad:**
+만약 이 rule를 숙지하고 실천한다면, 여러분은 많은 개발자들을 앞서게 될 것입니다.
+
+
+
+**나쁜 예:**
```python
from typing import List
@@ -312,7 +382,9 @@ def email_clients(clients: List[Client]) -> None:
email(client)
```
-**Good**:
+
+
+**좋은 예 1**:
```python
from typing import List
@@ -339,9 +411,13 @@ def email_clients(clients: List[Client]) -> None:
email(client)
```
-Do you see an opportunity for using generators now?
+
-**Even better**
+위 코드에서 generator를 사용할 수 있는 부분이 보이시나요?
+
+
+
+**좋은 예 2:**
```python
from typing import Generator, Iterator
@@ -367,33 +443,43 @@ def email_client(clients: Iterator[Client]) -> None:
email(client)
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
-### Function arguments (2 or fewer ideally)
+### 함수의 매개변수 (이상적으로 2개 이하)
-A large amount of parameters is usually the sign that a function is
-doing too much (has more than one responsibility). Try to decompose it
-into smaller functions having a reduced set of parameters, ideally less than
-three.
+매개변수의 수가 많다는 것은 일반적으로 함수가 너무 많은 일을 수행한다는 것을 의미합니다. (has more than one responsibility)
-If the function has a single responsibility, consider if you can bundle
-some or all parameters into a specialized object that will be passed as an
-argument to the function. These parameters might be attributes of a single
-entity that you can represent with a dedicated data structure. You may also
-be able to reuse this entity elsewhere in your program. The reason why this is
-a better arrangement is than having multiple parameters is that we may be able
-to move some computations, done with those parameters inside the
-function, into methods belonging to the new object, therefore reducing the
-complexity of the function.
+때문에 매개변수의 개수를 제한한다면 함수를 더 쉽게 테스트 할 수 있습니다.
-**Bad:**
+매개변수가 많은 함수를 매개변수가 적은 함수로 분해할 수 있다면 해보세요. 이상적으로는 3개 미만입니다.
+
+
+
+함수에 대해 단일 책임(single responsibility)이 있는 경우 여러 개의 매개변수를 하나의 특수한 개체로 묶을 수 있는지도 살펴보세요.
+
+프로그램에서 다른 곳에 매개변수를 재사용해야 하는 상황이 온다면 이 개체를 요긴하게 사용할 수 있습니다.
+
+
+
+또한 이 방법이 여러 개의 매개변수를 갖는 것 보다 더 나은 이유는
+
+함수 내부의 매개변수를 사용하여 수행되는 연산들을 또 하나의 함수로 만들어 복잡성을 줄일 수 있기 때문입니다.
+
+
+
+
+**나쁜 예:**
```python
def create_menu(title, body, button_text, cancellable):
pass
```
-**Java-esque**:
+
+
+**java-esque (자바 표현법)**:
```python
class Menu:
@@ -413,7 +499,9 @@ menu = Menu(
)
```
-**Also good**
+
+
+**좋은 예 1:**
```python
class MenuConfig:
@@ -447,7 +535,9 @@ config.cancellable = True
create_menu(config)
```
-**Fancy**
+
+
+**좋은 예 2:**
```python
from typing import NamedTuple
@@ -482,7 +572,9 @@ create_menu(
)
```
-**Even fancier**
+
+
+**좋은 예 3:**
```python
from dataclasses import astuple, dataclass
@@ -518,7 +610,9 @@ create_menu(
)
```
-**Even fancier, Python3.8+ only**
+
+
+**좋은 예 4 (Python3.8+ only)**
```python
from typing import TypedDict
@@ -555,11 +649,16 @@ create_menu(
)
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
+
-### Function names should say what they do
+### 함수의 이름은 함수가 수행하는 작업을 나타내야 합니다.
-**Bad:**
+
+
+**나쁜 예:**
```python
class Email:
@@ -572,7 +671,9 @@ message = Email()
message.handle()
```
-**Good:**
+
+
+**좋은 예:**
```python
class Email:
@@ -584,14 +685,19 @@ message = Email()
message.send()
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
-### Functions should only be one level of abstraction
+
-When you have more than one level of abstraction, your function is usually
-doing too much. Splitting up functions leads to reusability and easier testing.
+### 함수에는 추상화(abstraction)가 한 층만 있어야 합니다.
-**Bad:**
+만약 함수에 추상적인 층이 하나 이상 있다면, 함수가 너무 복잡해집니다.
+
+추상층이 여러 개 있다면, 그것들을 함수로 분해하여 재사용성을 높이고 테스트에 용이하도록 하는 것이 좋습니다.
+
+
+
+**나쁜 예:**
```python
# type: ignore
@@ -615,7 +721,9 @@ def parse_better_js_alternative(code: str) -> None:
pass
```
-**Good:**
+
+
+**좋은 예:**
```python
from typing import Tuple, List, Dict
@@ -651,15 +759,19 @@ def parse(tokens: List) -> List:
return syntax_tree
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
-### Don't use flags as function parameters
+### 함수의 매개변수로 flag를 사용하지 마세요.
-Flags tell your user that this function does more than one thing. Functions
-should do one thing. Split your functions if they are following different code
-paths based on a boolean.
+flag는 사용자로 하여금 이 함수가 두가지 이상의 기능을 수행한다는 것으로 보여질 수 있습니다.
-**Bad:**
+함수는 한가지 일을 해야합니다. bool을 기준으로 함수의 기능이 완전히 바뀐다면 함수를 분할해보세요.
+
+
+
+**나쁜 예:**
```python
from tempfile import gettempdir
@@ -673,7 +785,9 @@ def create_file(name: str, temp: bool) -> None:
Path(name).touch()
```
-**Good:**
+
+
+**좋은 예:**
```python
from tempfile import gettempdir
@@ -688,29 +802,35 @@ def create_temp_file(name: str) -> None:
(Path(gettempdir()) / name).touch()
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
+
+
+
+### 함수는 부작용(side effect)을 피해야 합니다.
+
+여기서 말하는 부작용(side effect)은 부정적인 의미가 아닙니다.
+
+함수는 일반적으로 매개변수를 받은 후 일련의 작업을 거쳐 값을 반환합니다.
+
+만약 값을 반환하는 것 이외에 다른 작업을 추가로 수행하는 경우 이 행위를 부작용이라 부릅니다.
+
+
+
+예를 들어 부작용으로 파일에 글을 쓸 수도 있으며, 파일의 특정 변수를 수정할 수도 있고, 실수로 모든 돈을 낯선 사람에게 송금할 수도 있습니다.
+
+만약 부작용을 꼭 필요로 한다면, 부작용이 유발되는 위치를 표시해주는 것이 좋습니다.
+
+또한 다른 함수나 클래스가 동시에 동일한 파일을 조작하지 않도록 하고 특정 함수를 통해 파일을 이 파일을 조작하도록 합시다.
-### Avoid side effects
+
-A function produces a side effect if it does anything other than take a value
-in and return another value or values. For example, a side effect could be
-writing to a file, modifying some global variable, or accidentally wiring all
-your money to a stranger.
+주요 요점은 개체 간 상태 공유, 가변 데이터 등을 사용하여 모든 함수 또는 변수가 이러한 데이터(파일 혹은 파일 내 데이터)를 조작할 수 있게 되는 일반적인 함정은 피할 필요가 있습니다.
-Now, you do need to have side effects in a program on occasion - for example,
-like in the previous example, you might need to write to a file. In these
-cases, you should centralize and indicate where you are incorporating side
-effects. Don't have several functions and classes that write to a particular
-file - rather, have one
-(and only one) service that does it.
+만약 이것을 잘 지킨다면, 다른 프로그래머들보다 오류를 찾기 더 수월해질 것입니다.
-The main point is to avoid common pitfalls like sharing state between objects
-without any structure, using mutable data types that can be written to by
-anything, or using an instance of a class, and not centralizing where your side
-effects occur. If you can do this, you will be happier than the vast majority
-of other programmers.
+
-**Bad:**
+**나쁜 예:**
```python
# type: ignore
@@ -739,7 +859,9 @@ print(fullname) # ["Ryan", "McDermott"]
# function again?
```
-**Good:**
+
+
+**좋은 예 1:**
```python
from typing import List, AnyStr
@@ -755,7 +877,9 @@ name, surname = split_into_first_and_last_name(fullname)
print(name, surname) # => Ryan McDermott
```
-**Also good**
+
+
+**좋은 예 2:**
```python
from dataclasses import dataclass
@@ -776,23 +900,29 @@ print(person.name) # => "Ryan McDermott"
print(person.name_as_first_and_last) # => ["Ryan", "McDermott"]
```
-**[⬆ back to top](#table-of-contents)**
+**[⬆ 목차로 이동](#목차)**
-## **Classes**
+
-### **Single Responsibility Principle (SRP)**
+## **클래스**
-Robert C. Martin writes:
+### 단일 책임 원칙 (Single Responsibility Principle; SRP)
-> A class should have only one reason to change.
+*설명하기에 앞서 책임(responsibility)를 이해를 위해 기능으로 해석했음을 미리 알려드리겠습니다.*
-"Reasons to change" are, in essence, the responsibilities managed by a class or
-function.
+
-In the following example, we create an HTML element that represents a comment
-with the version of the document:
+Robert C. Martin이 말하기를...:
-**Bad**
+> Class가 변경될 이유는 단 하나여야 한다. (A class should have only one reason to change.)
+
+"변경되야 할 이유"는 클래스 또는 함수가 담당하는 기능에 대응합니다.
+
+다음 예제에서는 HTML 주석을 만들고 주석에 pip의 버전을 기록합니다:
+
+
+
+**나쁜 예:**
```python
from importlib import metadata
@@ -813,16 +943,18 @@ class VersionCommentElement:
VersionCommentElement().render()
```
-This class has two responsibilities:
+
-- Retrieve the version number of the Python package
-- Render itself as an HTML element
+위 클래스는 두가지 기능이 있습니다.
+- pip 버전 정보를 획득합니다.
+- HTML 주석을 생성합니다.
-Any change to one or the other carries the risk of impacting the other.
+다만 위 코드에서 특정 기능을 변경하면 다른 기능에 영향을 미칩니다.
+우리는 이 두 기능을 분해할 수 있습니다.
-We can rewrite the class and decouple these responsibilities:
+
-**Good**
+**좋은 예:**
```python
from importlib import metadata
@@ -847,37 +979,44 @@ class VersionCommentElement:
VersionCommentElement(get_version("pip")).render()
```
-The result is that the class only needs to take care of rendering itself. It
-receives the version text during instantiation and this text is generated by
-calling a separate function, `get_version()`. Changing the class has no impact
-on the other, and vice-versa, as long as the contract between them does not
-change, i.e. the function provides a string and the class `__init__` method
-accepts a string.
+
-As an added bonus, the `get_version()` is now reusable elsewhere.
+위와 같이 작성하면 이 클래스는 **HTML 요소를 생성하는 것에만 집중**하게 됩니다.
-### **Open/Closed Principle (OCP)**
+인스턴스화할 때 버전 번호가 초기 매개변수로 전달됩니다. (`get_version()`을 통해 버전 정보를 얻음)
-> “Incorporate new features by extending the system, not by making
-> modifications (to it)”,
-> Uncle Bob.
+클래스 및 함수는 서로 격리되어 있으며 버전 사항이 다른 항목에는 영향을 미치지 않습니다.
-Objects should be open for extension, but closed to modification. It should be
-possible to augment the functionality provided by an object (for example, a
-class)
-without changing its internal contracts. An object can enable this when it is
-designed to be extended cleanly.
+또한 `get_version()`은 재사용될 수 있습니다.
-In the following example, we try to implement a simple web framework that
-handles HTTP requests and returns responses. The `View` class has a single
-method `.get()` that will be called when the HTTP server will receive a GET
-request from a client.
+**[⬆ 목차로 이동](#목차)**
-`View` is intentionally simple and returns `text/plain` responses. We would
-also like to return HTML responses based on a template file, so we subclass it
-using the `TemplateView` class.
+
-**Bad**
+### 개방/폐쇄 원칙 (Open/Closed Principle; OCP)
+
+소프트웨어의 객체(클래스, 함수 등)는 확장(extension)에 대해 열려 있어야 하지만, 수정(modification)에는 닫혀있어야 합니다.
+
+클래스 같은 개체는 내부 논리를 수정하지 않고 새로운 기능을 추가할 수 있도록 보장해야합니다.
+(원래 코드를 수정하지 않으면서 코드를 추가할 수 있어야 한다는 의미와 같습니다.)
+
+즉, 객체는 설계 초기에 확장성을 보장해야 합니다.
+
+다음 예에서는 HTTP 요청에 응답하는 간단한 웹 프레임워크를 구현하는 코드입니다.
+
+HTTP 서버에서 GET 요청을 받으면 `View` 클래스 `.get()` 메소드가 호출됩니다.
+
+
+
+`View`는 단순히 `text/plain`만 반환합니다.
+
+하지만 우리는 `text/HTML`의 형태로 받기를 원합니다.
+
+그래서 우리는 `View` 클래스를 상속받아 `TemplateView` 클래스를 만들었습니다.
+
+
+
+**나쁜 예:**
```python
from dataclasses import dataclass
@@ -918,17 +1057,22 @@ class TemplateView(View):
```
-The `TemplateView` class has modified the internal behaviour of its parent
-class in order to enable the more advanced functionality. In doing so, it now
-relies on the `View` to not change the implementation of the `.get()`
-method, which now needs to be frozen in time. We cannot introduce, for example,
-some additional checks in all our `View`-derived classes because the behaviour
-is overridden in at least one subtype and we will need to update it.
+
-Let's redesign our classes to fix this problem and let the `View` class be
-extended (not modified) cleanly:
+새로운 기능을 구현하기 위해 `TemplateView`는 `View`를 상속받고 `.get()` 메소드를 다시 썼습니다.
-**Good**
+위 코드는 부모 클래스의 `.get()`을 변경하지 않고 자식 클래스에서 오버라이딩 한 경우입니다.
+
+
+
+만약 위와 같은 방식으로 기능이 여러 개로 파생된다면,
+테스트를 수행할 때 `View` 클래스의 모든 자식 클래스에 대해 테스트 기능을 추가해야할 가능성이 있습니다.
+
+이 문제를 해결하기 위해 코드를 다시 설계하고 `View` 클래스가 깨끗하게 확장되도록 합시다.
+
+
+
+**좋은 예 1:**
```python
from dataclasses import dataclass
@@ -972,30 +1116,32 @@ class TemplateView(View):
with open(self.template_file) as fd:
return fd.read()
-
```
-Note that we did need to override the `render_body()` in order to change the
-source of the body, but this method has a single, well defined responsibility
-that **invites subtypes to override it**. It is designed to be extended by its
-subtypes.
+
+
+응답 내용을 변경하려면 `render_body()`를 재정의해야 하지만
+이 메소드는 하위 유형을 재정의하도록 요청하는 잘 정의된 단일 책임(single reponsibility)이 있습니다.
+
+그러나 이 방법은 자식 클래스가 기능을 확장하기 위해 여전히 재정의해야 합니다.
+
+상속(inheritance)과 컴포지션(composition)의 장점을 모두 사용하는 또 다른 좋은 방법은
+[Mixins](https://docs.djangoproject.com/en/4.1/topics/class-based-views/mixins/)을 사용하는 방법입니다.
+
+Mixins은 다른 관련 클래스들과는 독립적으로 사용 가능한 bare-bones classes입니다.
-Another good way to use the strengths of both object inheritance and object
-composition is to
-use [Mixins](https://docs.djangoproject.com/en/4.1/topics/class-based-views/mixins/)
-.
+target의 동작(behaviour)을 변경하기 위해 다중 상속을 사용하여 target 클래스와 "mixed-in" 됩니다.
-Mixins are bare-bones classes that are meant to be used exclusively with other
-related classes. They are "mixed-in" with the target class using multiple
-inheritance, in order to change the target's behaviour.
+
-A few rules:
+Rules:
+- Mixins는 항상 `object`에서 상속되어야 합니다.
+- Mixins는 항상 target 클래스 앞에 위치해야 합니다.
+ - e.g. `Foo(MixinA, MixinB, TargetClass)`
-- Mixins should always inherit from `object`
-- Mixins always come before the target class,
- e.g. `class Foo(MixinA, MixinB, TargetClass): ...`
+
-**Also good**
+**좋은 예 2:**
```python
from dataclasses import dataclass, field
@@ -1068,35 +1214,44 @@ class TemplateView(TemplateRenderMixin, ContentLengthMixin, View):
```
-As you can see, Mixins make object composition easier by packaging together
-related functionality into a highly reusable class with a single
-responsibility, allowing clean decoupling. Class extension is achieved by "
-mixing-in" the additional classes.
+
-The popular Django project makes heavy use of Mixins to compose its class-based
-views.
+위 코드에서 볼 수 있듯이, `Mixins`는 관련 기능을 재사용 가능한 클래스로 캡슐화함으로써
-FIXME: re-enable typechecking for the line above once it's clear how to use
-`typing.Protocol` to make the type checker work with Mixins.
+더 쉽게 패키징할 수 있게 되었으며, 단일 책임 원칙(SRP)에도 부합합니다.
-### **Liskov Substitution Principle (LSP)**
+
-> “Functions that use pointers or references to base classes
-> must be able to use objects of derived classes without knowing it”,
-> Uncle Bob.
+`Django`도 여러 가지의 `View` 클래스를 구성하기 위해 Mixins를 많이 사용했습니다.
-This principle is named after Barbara Liskov, who collaborated with fellow
-computer scientist Jeannette Wing on the seminal paper
-*"A behavioral notion of subtyping" (1994). A core tenet of the paper is that
-"a subtype (must) preserve the behaviour of the supertype methods and also all
-invariant and history properties of its supertype".
+FIXME: `typing.Protocol`의 사용 방식이 정립되었기 때문에 `Mixins`에 type 검사를 추가해야 합니다.
-In essence, a function accepting a supertype should also accept all its
-subtypes with no modification.
+
-Can you spot the problem with the following code?
+**[⬆ 목차로 이동](#목차)**
-**Bad**
+
+
+### 리스코프 치환 원칙 (Liskov Substitution Principle; LSP)
+
+
+
+> "부모 클래스의 포인터나 참조를 사용하는 함수는
+> 부모 클래스로부터 파생된 자식 클래스에 대해 몰라도 사용할 수 있어야 해.", Uncle Bob.
+
+
+
+이 원칙은 *A behavioral notion of subtyping (1994)* 논문의 저자 Jeannette Wing과 협력한 Barbara Liskov의 이름을 따서 명명되었습니다.
+
+이 논문의 핵심 원칙은 "subtype이 supertype와 동일한 방법과 동일한 기능과 동일 행동을 유지해야 한다"는 것입니다.
+
+다시 말해 supertype의 함수는 별도의 수정 없이 모든 subtype을 수용할 수 있어야 합니다.
+
+
+
+아래의 코드에서 어떤 문제가 있는지 확인해보도록 합시다.
+
+**나쁜 예:**
```python
from dataclasses import dataclass
@@ -1150,16 +1305,26 @@ def render(view: View, request) -> Response:
```
-The expectation is that `render()` function will be able to work with `View`
-and its subtype `TemplateView`, but the latter has broken compatibility by
-modifying the signature of the `.get()` method. The function will raise
-a `TypeError`
-exception when used with `TemplateView`.
+
+
+`render()` 메소드는 `View` 클래스 및 하위 클래스인 `TemplateView`와 함께 사용할 수 있어야 합니다.
+
+하지만 `TemplateView`는 상속 시 `.get()` 메소드의 signature(메소드의 입/출력)을 변경했습니다.
+
+`TemplateView`의 `render()`를 사용할 경우 오류가 발생할 것입니다.
+
+
+
+만약 우리가 `render()`를 `View`와 `View`의 파생 클래스에서 사용할 수 있기를 원한다면,
+
+우리는 외부 인터페이스가 손상되지 않도록 주의해야 할 필요가 있습니다.
+
+그런데 주어진 클래스에의 구성을 어떻게 알 수 있을까요?
+
+[mypy](https://mypy.readthedocs.io/en/stable/)와 같은 type 검사 도구를 사용하면
+이와 비슷한 문제가 발생할 때의 오류를 확인할 수 있습니다.
-If we want the `render()` function to work with any subtype of `View`, we must
-pay attention not to break its public-facing protocol. But how do we know what
-constitutes it for a given class? Type hinters like *mypy* will raise an error
-when it detects mistakes like this:
+
```
error: Signature of "get" incompatible with supertype "View"
@@ -1169,23 +1334,39 @@ error: Signature of "get" incompatible with supertype "View"
:36: note: def get(self, request: Any, template_file: str) -> Response
```
-### **Interface Segregation Principle (ISP)**
+
-> “Keep interfaces small
-> so that users don’t end up depending on things they don’t need.”,
-> Uncle Bob.
+**[⬆ 목차로 이동](#목차)**
-Several well known object oriented programming languages, like Java and Go,
-have a concept called interfaces. An interface defines the public methods and
-properties of an object without implementing them. They are useful when we
-don't want to couple the signature of a function to a concrete object; we'd
-rather say "I don't care what object you give me, as long as it has certain
-methods and attributes I expect to make use of".
+
-Python does not have interfaces. We have Abstract Base Classes instead, which
-are a little different, but can serve the same purpose.
+### 인터페이스 분리 원칙 (Interface Segregation Principle; ISP)
-**Good**
+
+
+> “사용자가 필요없는 것에 의존하지 않도록 인터페이스를 간결하게 만드는 건 어때?", Uncle Bob.
+
+
+
+Java나 Go와 같은 유명한 객체 지향 프로그래밍 언어에서는 인터페이스(interface)라는 개념이 있습니다.
+
+인터페이스 클래스는 공개 메소드와 속성을 구현하지 않고 정의합니다.
+
+함수의 signature(함수의 입/출력)를 정의하고 싶지만 구체적으로 구현하고 싶지 않을 때 인터페이스는 매우 유용하게 사용됩니다.
+
+
+
+우리는 "당신이 나에게 전달한 대상의 세부 사항에 대해서는 관심이 없고, 내가 사용할 수 있는 방법이나 속성에만 관심이 있다."고 말할 수 있습니다.
+
+
+
+*Python에는 인터페이스가 없습니다.*
+
+다만 인터페이스와는 약간 다르지만 추상 클래스를 사용하여 동일한 기능을 구현할 수 있습니다.
+
+
+
+**좋은 예:**
```python
@@ -1219,13 +1400,19 @@ def welcome_user(user_name: str, actor: Greeter):
welcome_user("Barbara", FriendlyActor())
```
-Now imagine the following scenario: we have a certain number of PDF documents
-that we author and want to serve to our web site visitors. We are using a
-Python web framework and we might be tempted to design a class to manage these
-documents, so we go ahead and design a comprehensive abstract base class for
-our document.
+
+
+이제 다음의 시나리오를 상상해봅시다.
+
+PDF 문서가 몇 개 있는데, 우리 웹 사이트 사용자에게 PDF 파일을 제공하고 싶습니다.
+
+우리는 파이썬 웹 프레임워크를 사용하여 이러한 문서를 관리하기 위한 클래스를 설계하고자 합니다.
-**Error**
+그래서 우리는 문서에 추상 클래스를 설계했는데, 이 클래스에 사용할 수 있는 모든 기능들을 적어두었습니다.
+
+
+
+**에러 발생:**
```python
import abc
@@ -1274,28 +1461,31 @@ def view(request):
```
-But we can't! If we don't implement the `.save()` method, an exception will be
-raised:
+
+
+하지만 안되더라고요! `.save()` 메소드를 구현하지 않으면 예외가 발생합니다.
```
Can't instantiate abstract class PDFDocument with abstract method save.
```
-That's annoying. We don't really need to implement `.save()` here. We could
-implement a dummy method that does nothing or raises `NotImplementedError`, but
-that's useless code that we will need to maintain.
+이건 짜증나네요. 우리는 `.save()`를 구현할 필요가 없습니다.
-At the same time, if we remove `.save()` from the abstract class now we will
-need to add it back when we will later implement a way for users to submit
-their documents, bringing us back to the same situation as before.
+우리는 아무것도 하지 않거나 `NotImplementedError`를 발생시키는 더미 메소드를 구현할 수 있지만, 그것또한 쓸모없는 코드가 됩니다.
-The problem is that we have written an *interface* that has features we don't
-need right now as we are not using them.
+
-The solution is to decompose the interface into smaller and composable
-interfaces that segregate each feature.
+동시에 만약 우리가 추상 클래스에서 `.save()`를 제거한다면, 사용자가 문서를 save 할 때 다시 추가해야 합니다.
-**Good**
+
+
+문제를 요약하면, 우리는 인터페이스를 썼고, 이 인터페이스에는 현재 사용할 수 없는 몇가지 특성이 있다는 것입니다.
+
+이 문제에 대한 해결방식은 이 인터페이스를 더 작은 인터페이스로 분해하고, 각각의 새로운 인터페이스가 일부 내용을 담당하도록 만드는 것입니다.
+
+
+
+**좋은 예:**
```python
import abc
@@ -1347,14 +1537,25 @@ def view(request):
```
-### **Dependency Inversion Principle (DIP)**
+
+
+**[⬆ 목차로 이동](#목차)**
-> “Depend upon abstractions, not concrete details”,
-> Uncle Bob.
+
-Imagine we wanted to write a web view that returns an HTTP response that
-streams rows of a CSV file we create on the fly. We want to use the CSV writer
-that is provided by the standard library.
+### 의존성 역전 원칙 (Dependency Inversion Principle; DIP)
+
+
+
+> "구체적인 세부 사항(details)보다는 추상(abstractions)에 의존하는 건 어때?", Uncle Bob.
+
+
+
+CSV 파일의 행을 즉시 스트리밍하는 HTTP Response를 반환하는 web view를 작성하고 싶다고 생각해보세요.
+
+우리는 파이썬 표준 라이브러리에서 제공하는 CSV writer를 사용하고자 합니다.
+
+
**Bad**
@@ -1395,16 +1596,21 @@ def some_view(request):
```
-Our first implementation works around the CSV's writer interface by
-manipulating a `StringIO` object (which is file-like) and performing several
-low level operations in order to farm out the rows from the writer. It's a lot
-of work and not very elegant.
+
+
+첫 구현은 CSV writer 인터페이스를 사용했습니다.
+
+일부 하위 작업은 파일처럼 String I/O 객체를 조작하여 writer에 데이터를 썼습니다.
+
+이 방법은 번잡하고 우아하지 않습니다.
+
+
+
+더 좋은 방법은 writer가 `.write()` 메소드를 포함하는 객체만 필요로 한다는 것을 이해하는 것입니다.
+
+`StreamingHttpResponse` 클래스가 즉시 클라이언트로 다시 스트리밍할 수 있도록 새로운 행 데이터를 즉시 반환하는 dummy 객체를 전달하는 것은 어떤가요?
-A better way is to leverage the fact that the writer just needs an object with
-a `.write()` method to do our bidding. Why not pass it a dummy object that
-immediately returns the newly assembled row, so that
-the `StreamingHttpResponse`
-class can immediate stream it back to the client?
+
**Good**
@@ -1438,44 +1644,67 @@ def some_streaming_csv_view(request):
```
-Much better, and it works like a charm! The reason it's superior to the
-previous implementation should be obvious: less code (and more performant) to
-achieve the same result. We decided to leverage the fact that the writer class
-depends on the `.write()` abstraction of the object it receives, without caring
-about the low level, concrete details of what the method actually does.
+
-This example was taken from
-[a submission made to the Django documentation](https://code.djangoproject.com/ticket/21179)
-by this author.
+위와 같이 구현하면 이전의 것보다 훨씬 낫고 우아해집니다.
-**[⬆ back to top](#table-of-contents)**
+더 적은 코드로 동일한 기능을 구현했다는 것은 장점이 분명합니다.
-## **Don't repeat yourself (DRY)**
+우리는 writer 클래스에서 `.write()`라는 추상적인 방법에만 관심이 있고 내부 세부 사항에는 관심이 없다는 것을 활용했습니다.
-Try to observe the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)
-principle.
+이 예제는 [a submission made to the Django document](https://code.djangoproject.com/ticket/21179)에서 가지고 온 것입니다.
-Do your absolute best to avoid duplicate code. Duplicate code is bad because it
-means that there's more than one place to alter something if you need to change
-some logic.
+
-Imagine if you run a restaurant and you keep track of your inventory: all your
-tomatoes, onions, garlic, spices, etc. If you have multiple lists that you keep
-this on, then all have to be updated when you serve a dish with tomatoes in
-them. If you only have one list, there's only one place to update!
+**[⬆ 목차로 이동](#목차)**
-Often you have duplicate code because you have two or more slightly different
-things, that share a lot in common, but their differences force you to have two
-or more separate functions that do much of the same things. Removing duplicate
-code means creating an abstraction that can handle this set of different things
-with just one function/module/class.
+
-Getting the abstraction right is critical. Bad abstractions can be worse than
-duplicate code, so be careful! Having said this, if you can make a good
-abstraction, do it! Don't repeat yourself, otherwise you'll find yourself
-updating multiple places any time you want to change one thing.
+## **반복은 지양합시다. (Don't repeat yourself; DRY)**
-**Bad:**
+위키피디아의 [중복 배제 원칙](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 문서를 살펴보고 오세요.
+
+
+
+중복 코드는 코드 로직을 수정할 때 중복되는 부분도 동시에 수정해야 한다는 것을 의미합니다.
+
+중복 코드가 많으면 많을 수록 수정 작업량이 많아질 수 밖에 없고 오류 발생 가능성 또한 높아지게 됩니다.
+
+
+
+식당을 운영하고 토마토, 양파, 마늘, 향신료 등 재고를 조사한다고 생각해봅시다.
+
+리스트가 여러 개 있으면 토마토 하나로 토마토가 들어간 요리를 만들었을 때 여러 개의 리스트를 전부 업데이트 해야 합니다.
+
+반대로 목록이 하나만 있으면 하나만 업데이트 하면 됩니다.
+
+
+
+공통점이 많지만 코드 상에 약간 다른 것이 있어서 중복 코드를 사용하는 경우가 종종 있습니다.
+
+하지만 그 차이로 인해 동일한 작업을 수행하는 두 개 이상의 개별 함수가 필요합니다.
+
+중복 코드를 제거하려면 먼저 공통 부분을 추상화한 다음 하나의 함수/모듈/클래스로 다른 부분을 처리해야 합니다.
+
+
+
+추상적 사고를 잘 하는 것은 프로그래머에게 있어 매우 중요한 스킬 중 하나입니다.
+
+나쁜 추상적 사고로 인한 피해는 때때로 중복 코드보다 더 심각한 문제에 직면할 수 있습니다.
+
+만약 추상적 사고를 잘 할 수 있다면, 그렇게 하셔야 합니다! 중복 코드를 작성하지 맙시다.
+
+이를 지키지 않는다면 로직을 변경하고자 할 때 변경해야할 부분이 많다는 것을 곧 알게 될 것입니다.
+
+
+
+**추상적 사고를 잘 한다는 것:**
+번역을 하며 제일 이해하기 어려웠던 부분이 바로 abstraction이라는 말인데,
+맥락을 보면 공통적이고 본질적인 부분만 추출하고 개별적인 부분은 버린다는 의미 같습니다.
+
+
+
+**나쁜 예:**
```python
from typing import List, Dict
@@ -1547,6 +1776,8 @@ company_managers = [
company_managers_list = get_manager_list(managers=company_managers)
```
+
+
**Good:**
```python
@@ -1594,11 +1825,15 @@ company_managers = [
company_managers_list = get_employee_list(employees=company_managers)
```
-**[⬆ back to top](#table-of-contents)**
+
+
+**[⬆ 목차로 이동](#목차)**
+
+
## **Translations**
-This document is also available in other languages:
+이 문서는 다양한 언어로 번역되었습니다:
- 🇨🇳 **
Chinese** [yinruiqing/clean-code-python](https://github.com/yinruiqing/clean-code-python)
@@ -1608,4 +1843,8 @@ This document is also available in other languages:
- 🇮🇷 **
Persian:** [SepehrRasouli/clean-code-python](https://github.com/SepehrRasouli/clean-code-python)
-**[⬆ back to top](#table-of-contents)**
+
+
+**[⬆ 목차로 이동](#목차)**
+
+