├── .gitattributes ├── .travis.yml ├── LICENSE ├── .travis-build.php └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-documentation=false 2 | *.md linguist-language=PHP 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - nightly 7 | 8 | script: php .travis-build.php 9 | 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ryan McDermott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /.travis-build.php: -------------------------------------------------------------------------------- 1 | setFlags(SplFileObject::DROP_NEW_LINE); 6 | 7 | $cliRedBackground = "\033[37;41m"; 8 | $cliReset = "\033[0m"; 9 | $exitStatus = 0; 10 | 11 | $indentationSteps = 3; 12 | $manIndex = 0; 13 | $linesWithSpaces = []; 14 | $tableOfContentsStarted = null; 15 | $currentTableOfContentsChapters = []; 16 | $chaptersFound = []; 17 | foreach ($readMeFile as $lineNumber => $line) { 18 | if (preg_match('/\s$/', $line)) { 19 | $linesWithSpaces[] = sprintf('%5s: %s', 1 + $lineNumber, $line); 20 | } 21 | if (preg_match('/^(?##+)\s(?.+)/', $line, $matches)) { 22 | if (null === $tableOfContentsStarted) { 23 | $tableOfContentsStarted = true; 24 | continue; 25 | } 26 | $tableOfContentsStarted = false; 27 | 28 | $chaptersFound[] = sprintf('%s [%s](#%s)', 29 | strlen($matches['depth']) === 2 30 | ? sprintf(' %s.', ++$manIndex) 31 | : ' *' 32 | , 33 | $matches['title'], 34 | preg_replace(['/ /', '/[^-\w]+/'], ['-', ''], strtolower($matches['title'])) 35 | ); 36 | } 37 | if ($tableOfContentsStarted === true && isset($line[0])) { 38 | $currentTableOfContentsChapters[] = $line; 39 | } 40 | } 41 | 42 | if (count($linesWithSpaces)) { 43 | fwrite(STDERR, sprintf("${cliRedBackground}The following lines end with a space character:${cliReset}\n%s\n\n", 44 | implode(PHP_EOL, $linesWithSpaces) 45 | )); 46 | $exitStatus = 1; 47 | } 48 | 49 | $currentTableOfContentsChaptersFilename = __DIR__ . '/current-chapters'; 50 | $chaptersFoundFilename = __DIR__ . '/chapters-found'; 51 | 52 | file_put_contents($currentTableOfContentsChaptersFilename, implode(PHP_EOL, $currentTableOfContentsChapters)); 53 | file_put_contents($chaptersFoundFilename, implode(PHP_EOL, $chaptersFound)); 54 | 55 | $tableOfContentsDiff = shell_exec(sprintf('diff --unified %s %s', 56 | escapeshellarg($currentTableOfContentsChaptersFilename), 57 | escapeshellarg($chaptersFoundFilename) 58 | )); 59 | 60 | @ unlink($currentTableOfContentsChaptersFilename); 61 | @ unlink($chaptersFoundFilename); 62 | 63 | if (!empty($tableOfContentsDiff)) { 64 | fwrite(STDERR, sprintf("${cliRedBackground}The table of contents is not aligned:${cliReset}\n%s\n\n", 65 | $tableOfContentsDiff 66 | )); 67 | $exitStatus = 1; 68 | } 69 | 70 | exit($exitStatus); 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Code PHP - 한글판 2 | 3 | ## 목차 4 | 5 | 1. [들어가며](#들어가며) 6 | 2. [변수](#변수) 7 | * [의미있고 발음하기 쉬운 변수명을 사용하세요](#의미있고-발음하기-쉬운-변수명을-사용하세요) 8 | * [같은 타입의 변수에는 동일한 어휘를 사용하세요](#같은-타입의-변수에는-동일한-어휘를-사용하세요) 9 | * [찾기 쉬운 이름을 사용하세요 (part 1)](#찾기-쉬운-이름을-사용하세요-part-1) 10 | * [찾기 쉬운 이름을 사용하세요 (part 2)](#찾기-쉬운-이름을-사용하세요-part-2) 11 | * [설명적인 변수를 사용하세요](#설명적인-변수를-사용하세요) 12 | * [너무 깊은 중첩은 피하고 초기에 return 하세요 (part 1)](#너무-깊은-중첩은-피하고-초기에-return-하세요-part-1) 13 | * [너무 깊은 중첩은 피하고 초기에 return 하세요 (part 2)](#너무-깊은-중첩은-피하고-초기에-return-하세요-part-2) 14 | * [머릿속으로 짐작하게 하지 마세요](#머릿속으로-짐작하게-하지-마세요) 15 | * [불필요한 문맥을 덧붙이지 마세요](#불필요한-문맥을-덧붙이지-마세요) 16 | * [단락이나 조건문 대신 기본 인수를 사용하세요](#단락이나-조건문-대신-기본-인수를-사용하세요) 17 | 3. [비교 연산자](#비교-연산자) 18 | * [동일 비교 연산자를 사용하세요](#동일-비교-연산자를-사용하세요) 19 | 4. [함수](#함수) 20 | * [함수 인수 (2개 이하가 이상적)](#함수-인수-2개-이하가-이상적) 21 | * [함수는 한가지만 해야합니다](#함수는-한가지만-해야합니다) 22 | * [함수명은 어떤 일을 하는지 나타내야 합니다](#함수명은-어떤-일을-하는지-나타내야-합니다) 23 | * [함수는 추상화 레벨이 단 하나여야 합니다](#함수는-추상화-레벨이-단-하나여야-합니다) 24 | * [플래그를 함수의 매개변수로 사용하지 마세요](#플래그를-함수의-매개변수로-사용하지-마세요) 25 | * [부작용을 피하세요](#부작용을-피하세요) 26 | * [전역 함수를 사용하지 마세요](#전역-함수를-사용하지-마세요) 27 | * [싱글턴 패턴을 사용하지 마세요](#싱글턴-패턴을-사용하지-마세요) 28 | * [조건문은 캡슐화하세요](#조건문은-캡슐화하세요) 29 | * [부정 조건문을 피하세요](#부정-조건문을-피하세요) 30 | * [조건문을 피하세요](#조건문을-피하세요) 31 | * [타입 체킹을 피하세요 (part 1)](#타입-체킹을-피하세요-part-1) 32 | * [타입 체킹을 피하세요 (part 2)](#타입-체킹을-피하세요-part-2) 33 | * [불필요한 코드는 제거하세요](#불필요한-코드는-제거하세요) 34 | 5. [객체와 자료구조](#객체와-자료구조) 35 | * [객체 캡슐화를 사용하세요](#객체-캡슐화를-사용하세요) 36 | * [객체가 private/protected 멤버를 갖게 하세요](#객체가-privateprotected-멤버를-갖게-하세요) 37 | 6. [클래스](#클래스) 38 | * [상속보다는 컴포지션을 사용하세요](#상속보다는-컴포지션을-사용하세요) 39 | * [유창한 인터페이스를 피하세요](#유창한-인터페이스를-피하세요) 40 | 7. [SOLID](#solid) 41 | * [단일 책임 원칙 (SRP)](#단일-책임-원칙-srp) 42 | * [개방/폐쇄 원칙 (OCP)](#개방폐쇄-원칙-ocp) 43 | * [리스코브 치환 원칙 (LSP)](#리스코브-치환-원칙-lsp) 44 | * [인터페이스 분리 원칙 (ISP)](#인터페이스-분리-원칙-isp) 45 | * [의존성 역전 원칙 (DIP)](#의존성-역전-원칙-dip) 46 | 8. [반복하지 마세요 (DRY)](#반복하지-마세요-dry) 47 | 9. [번역](#번역) 48 | 49 | ## 들어가며 50 | 51 | 52 | Robert C. Martin의 책, 소프트웨어 엔지니어링의 교과서라고 불리는 [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) PHP 버전입니다. 53 | 이 문서는 스타일 가이드가 아닙니다. PHP로 읽기 쉽고 재사용 가능하며, 리팩토링이 쉬운 소프트웨어를 만들어내기 위한 안내서입니다. 54 | 55 | 문서의 모든 원칙을 엄격하게 지켜야 할 필요는 없습니다. 그리고 어떤 것들은 일반적으로 합의되지 못할 것입니다. 56 | 이 문서는 안내서일 뿐이고 그 이상이 될 순 없지만, 수년간 *Clean Code*의 저자들로부터 축적된 경험을 문서화 한 것입니다. 57 | 58 | [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)에 영감을 받아 작성되었습니다. 59 | 60 | 여전히 PHP 5를 사용하는 개발자가 많겠지만, 문서의 예제 대부분은 PHP 7.1 이상의 버전에서만 작동합니다. 61 | 62 | ## 변수 63 | 64 | ### 의미있고 발음하기 쉬운 변수명을 사용하세요 65 | 66 | **나쁜 예:** 67 | 68 | ```php 69 | $ymdstr = $moment->format('y-m-d'); 70 | ``` 71 | 72 | **좋은 예:** 73 | 74 | ```php 75 | $currentDate = $moment->format('y-m-d'); 76 | ``` 77 | 78 | **[⬆ 위로 가기](#목차)** 79 | 80 | ### 같은 타입의 변수에는 동일한 어휘를 사용하세요 81 | 82 | **나쁜 예:** 83 | 84 | ```php 85 | getUserInfo(); 86 | getUserData(); 87 | getUserRecord(); 88 | getUserProfile(); 89 | ``` 90 | 91 | **좋은 예:** 92 | 93 | ```php 94 | getUser(); 95 | ``` 96 | 97 | **[⬆ 위로 가기](#목차)** 98 | 99 | ### 찾기 쉬운 이름을 사용하세요 (part 1) 100 | 101 | 작성해야 할 코드보다 읽어야 할 코드가 더 많습니다. 우리가 작성해야 할 코드를 읽기 쉽고 찾기 쉽게 만드는 것은 중요합니다. 102 | 프로그램을 이해하기 쉽도록 변수명을 의미 있게 짓지 *않는*다면, 코드를 읽는 사람을 곤란하게 만드는 것입니다. 103 | 변수명을 찾기 쉽게 만드세요. 104 | 105 | 106 | **나쁜 예:** 107 | 108 | ```php 109 | // 도대체 448이 뭔 뜻이래요? 110 | $result = $serializer->serialize($data, 448); 111 | ``` 112 | 113 | **좋은 예:** 114 | 115 | ```php 116 | $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); 117 | ``` 118 | 119 | ### 찾기 쉬운 이름을 사용하세요 (part 2) 120 | 121 | **나쁜 예:** 122 | 123 | ```php 124 | // 4는 무슨 의미인가요? 125 | if ($user->access & 4) { 126 | // ... 127 | } 128 | ``` 129 | 130 | **좋은 예:** 131 | 132 | ```php 133 | class User 134 | { 135 | const ACCESS_READ = 1; 136 | const ACCESS_CREATE = 2; 137 | const ACCESS_UPDATE = 4; 138 | const ACCESS_DELETE = 8; 139 | } 140 | 141 | if ($user->access & User::ACCESS_UPDATE) { 142 | // 수정하세요... 143 | } 144 | ``` 145 | 146 | **[⬆ 위로 가기](#목차)** 147 | 148 | ### 설명적인 변수를 사용하세요 149 | 150 | **나쁜 예:** 151 | 152 | ```php 153 | $address = 'One Infinite Loop, Cupertino 95014'; 154 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 155 | preg_match($cityZipCodeRegex, $address, $matches); 156 | 157 | saveCityZipCode($matches[1], $matches[2]); 158 | ``` 159 | 160 | **나쁘진 않은 예:** 161 | 162 | 조금 낫군요. 하지만, 여전히 regex에 아주 많이 의존하고 있습니다. 163 | 164 | ```php 165 | $address = 'One Infinite Loop, Cupertino 95014'; 166 | $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; 167 | preg_match($cityZipCodeRegex, $address, $matches); 168 | 169 | [, $city, $zipCode] = $matches; 170 | saveCityZipCode($city, $zipCode); 171 | ``` 172 | 173 | **좋은 예:** 174 | 175 | naming subpattern을 사용해서 regex의 의존성을 줄입시다. 176 | 177 | ```php 178 | $address = 'One Infinite Loop, Cupertino 95014'; 179 | $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; 180 | preg_match($cityZipCodeRegex, $address, $matches); 181 | 182 | saveCityZipCode($matches['city'], $matches['zipCode']); 183 | ``` 184 | 185 | **[⬆ 위로 가기](#목차)** 186 | 187 | ### 너무 깊은 중첩은 피하고 초기에 return 하세요 (part 1) 188 | 189 | 너무 많은 if else 문은 코드를 따라가기 어렵게 만들 수 있습니다. 명료한 것이 암시하는 것보다 나아요. 190 | 191 | 192 | **나쁜 예:** 193 | 194 | ```php 195 | function isShopOpen($day): bool 196 | { 197 | if ($day) { 198 | if (is_string($day)) { 199 | $day = strtolower($day); 200 | if ($day === 'friday') { 201 | return true; 202 | } elseif ($day === 'saturday') { 203 | return true; 204 | } elseif ($day === 'sunday') { 205 | return true; 206 | } else { 207 | return false; 208 | } 209 | } else { 210 | return false; 211 | } 212 | } else { 213 | return false; 214 | } 215 | } 216 | ``` 217 | 218 | **좋은 예:** 219 | 220 | ```php 221 | function isShopOpen(string $day): bool 222 | { 223 | if (empty($day)) { 224 | return false; 225 | } 226 | 227 | $openingDays = [ 228 | 'friday', 'saturday', 'sunday' 229 | ]; 230 | 231 | return in_array(strtolower($day), $openingDays, true); 232 | } 233 | ``` 234 | 235 | **[⬆ 위로 가기](#목차)** 236 | 237 | ### 너무 깊은 중첩은 피하고 초기에 return 하세요 (part 2) 238 | 239 | **나쁜 예:** 240 | 241 | ```php 242 | function fibonacci(int $n) 243 | { 244 | if ($n < 50) { 245 | if ($n !== 0) { 246 | if ($n !== 1) { 247 | return fibonacci($n - 1) + fibonacci($n - 2); 248 | } else { 249 | return 1; 250 | } 251 | } else { 252 | return 0; 253 | } 254 | } else { 255 | return 'Not supported'; 256 | } 257 | } 258 | ``` 259 | 260 | **좋은 예:** 261 | 262 | ```php 263 | function fibonacci(int $n): int 264 | { 265 | if ($n === 0 || $n === 1) { 266 | return $n; 267 | } 268 | 269 | if ($n > 50) { 270 | throw new \Exception('Not supported'); 271 | } 272 | 273 | return fibonacci($n - 1) + fibonacci($n - 2); 274 | } 275 | ``` 276 | 277 | **[⬆ 위로 가기](#목차)** 278 | 279 | ### 머릿속으로 짐작하게 하지 마세요 280 | 281 | 우리의 코드를 읽는 사람이 변수가 어떤 의미인지 해석하게 하지 마세요. 282 | 명료한 것이 암시하는 것보다 좋아요. 283 | 284 | **나쁜 예:** 285 | 286 | ```php 287 | $l = ['Austin', 'New York', 'San Francisco']; 288 | 289 | for ($i = 0; $i < count($l); $i++) { 290 | $li = $l[$i]; 291 | doStuff(); 292 | doSomeOtherStuff(); 293 | // ... 294 | // ... 295 | // ... 296 | // 잠깐 $li가 뭐였죠? 297 | dispatch($li); 298 | } 299 | ``` 300 | 301 | **좋은 예:** 302 | 303 | ```php 304 | $locations = ['Austin', 'New York', 'San Francisco']; 305 | 306 | foreach ($locations as $location) { 307 | doStuff(); 308 | doSomeOtherStuff(); 309 | // ... 310 | // ... 311 | // ... 312 | dispatch($location); 313 | } 314 | ``` 315 | 316 | **[⬆ 위로 가기](#목차)** 317 | 318 | ### 불필요한 문맥을 덧붙이지 마세요 319 | 320 | 클래스/객체 명이 이미 말해주고 있다면, 변수명에 굳이 번복하지 마세요. 321 | 322 | **나쁜 예:** 323 | 324 | ```php 325 | class Car 326 | { 327 | public $carMake; 328 | public $carModel; 329 | public $carColor; 330 | 331 | //... 332 | } 333 | ``` 334 | 335 | **좋은 예:** 336 | 337 | ```php 338 | class Car 339 | { 340 | public $make; 341 | public $model; 342 | public $color; 343 | 344 | //... 345 | } 346 | ``` 347 | 348 | **[⬆ 위로 가기](#목차)** 349 | 350 | ### 단락이나 조건문 대신 기본 인수를 사용하세요 351 | 352 | **좋진 않은 예:** 353 | 354 | 아래 예제는 `$breweryName`이 `NULL`이 될 수 있으므로 좋지 않아요. 355 | 356 | ```php 357 | function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void 358 | { 359 |    // ... 360 | } 361 | ``` 362 | 363 | **나쁘진 않은 예:** 364 | 365 | 위의 예제보다 훨씬 이해하기 쉬워졌습니다. 하지만 변수의 값을 제어하는 게 낫겠습니다. 366 | 367 | ```php 368 | function createMicrobrewery($name = null): void 369 | { 370 |    $breweryName = $name ?: 'Hipster Brew Co.'; 371 | // ... 372 | } 373 | ``` 374 | 375 | **좋은 예:** 376 | 377 | [type hinting](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration)을 사용해서 `$breweryName`이 `NULL`이 될 수 없음을 확신할 수 있습니다. 378 | 379 | ```php 380 | function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void 381 | { 382 |    // ... 383 | } 384 | ``` 385 | 386 | **[⬆ 위로 가기](#목차)** 387 | 388 | ## 비교 연산자 389 | 390 | **[⬆ 위로 가기](#목차)** 391 | 392 | ### [동일 비교 연산자](http://php.net/manual/en/language.operators.comparison.php)를 사용하세요 393 | 394 | **좋진 않은 예:** 395 | 396 | ```php 397 | $a = '42'; 398 | $b = 42; 399 | 단순한 비교 연산자를 사용하면 string이 int로 변환 될 것입니다. 400 | 401 | if( $a != $b ) { 402 | //이 표현식은 항상 건너 뛸 것입니다. 403 | } 404 | 405 | ``` 406 | $a != $b는 false를 리턴하지만 사실은 true가 맞아요! 407 | string '42'는 int 42와 다릅니다. 408 | 409 | **좋은 예:** 410 | 동일 비교 연산자를 사용하면 자료형과 값을 비교합니다. 411 | ```php 412 | if( $a !== $b ) { 413 | //이 표현식은 확인되었습니다. 414 | } 415 | 416 | ``` 417 | $a !== $b는 true를 return 합니다. 418 | 419 | **[⬆ 위로 가기](#목차)** 420 | 421 | 422 | ## 함수 423 | 424 | ### 함수 인수 (2개 이하가 이상적) 425 | 426 | 함수 매개변수의 개수를 제한하는 것은 엄청나게 중요합니다. 함수를 테스트하는 것을 더 쉽게 만들어 주기 때문이죠. 3개 이상의 매개변수는 테스트 조합을 폭발하게 합니다. 매개변수마다 서로 다른 수많은 케이스를 테스트해야 하기 때문입니다. 427 | 428 | 매개변수가 없는 것(0개)이 가장 이상적입니다. 1개에서 2개 정도는 괜찮습니다. 3개는 피해야 합니다. 매개변수가 3개를 넘어가면 통합되어야 합니다. 일반적으로, 매개변수가 2개 이상이라면 그 함수는 너무 많은 역할을 하는 것입니다. 그런 경우가 아니라면, 상위 레벨의 객체는 대부분 하나의 매개변수로 충분할 것입니다. 429 | 430 | **나쁜 예:** 431 | 432 | ```php 433 | function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void 434 | { 435 | // ... 436 | } 437 | ``` 438 | 439 | **좋은 예:** 440 | 441 | ```php 442 | class MenuConfig 443 | { 444 | public $title; 445 | public $body; 446 | public $buttonText; 447 | public $cancellable = false; 448 | } 449 | 450 | $config = new MenuConfig(); 451 | $config->title = 'Foo'; 452 | $config->body = 'Bar'; 453 | $config->buttonText = 'Baz'; 454 | $config->cancellable = true; 455 | 456 | function createMenu(MenuConfig $config): void 457 | { 458 | // ... 459 | } 460 | ``` 461 | 462 | **[⬆ 위로 가기](#목차)** 463 | 464 | ### 함수는 한가지만 해야합니다 465 | 466 | 소프트웨어 엔지니어링에서 가장 중요한 규칙입니다. 함수가 1개 이상의 역할을 하게 되면, 작성, 테스트, 추론하기가 어려워집니다. 함수를 단 하나의 행동으로 떼어낼 수 있다면, 코드는 쉽게 리팩토링 될 수 있으며 훨씬 더 깔끔하게 읽힐 것입니다. 이 문서에서 나머지는 다 갖다 버리고 이것만 지키신대도 웬만한 개발자보다 앞서게 될 것입니다. 467 | 468 | **나쁜 예:** 469 | ```php 470 | function emailClients(array $clients): void 471 | { 472 | foreach ($clients as $client) { 473 | $clientRecord = $db->find($client); 474 | if ($clientRecord->isActive()) { 475 | email($client); 476 | } 477 | } 478 | } 479 | ``` 480 | 481 | **좋은 예:** 482 | 483 | ```php 484 | function emailClients(array $clients): void 485 | { 486 | $activeClients = activeClients($clients); 487 | array_walk($activeClients, 'email'); 488 | } 489 | 490 | function activeClients(array $clients): array 491 | { 492 | return array_filter($clients, 'isClientActive'); 493 | } 494 | 495 | function isClientActive(int $client): bool 496 | { 497 | $clientRecord = $db->find($client); 498 | 499 | return $clientRecord->isActive(); 500 | } 501 | ``` 502 | 503 | **[⬆ 위로 가기](#목차)** 504 | 505 | ### 함수명은 어떤 일을 하는지 나타내야 합니다. 506 | 507 | **나쁜 예:** 508 | 509 | ```php 510 | class Email 511 | { 512 | //... 513 | 514 | public function handle(): void 515 | { 516 | mail($this->to, $this->subject, $this->body); 517 | } 518 | } 519 | 520 | $message = new Email(...); 521 | // 이게 뭘까요? 메세지를 위한 핸들? 지금 우리가 파일을 작성할건가요? 522 | $message->handle(); 523 | ``` 524 | 525 | **좋은 예:** 526 | 527 | ```php 528 | class Email 529 | { 530 | //... 531 | 532 | public function send(): void 533 | { 534 | mail($this->to, $this->subject, $this->body); 535 | } 536 | } 537 | 538 | $message = new Email(...); 539 | // 분명하고 명백하군요 540 | $message->send(); 541 | ``` 542 | 543 | **[⬆ 위로 가기](#목차)** 544 | 545 | ### 함수는 추상화 레벨이 단 하나여야 합니다 546 | 547 | 추상화 레벨이 하나 이상이라면, 보통 우리의 함수는 너무 많은 일을 하고 있는 것입니다. 함수를 분리하면 재사용성을 높일 수 있고 테스트가 용이해집니다. 548 | 549 | **나쁜 예:** 550 | 551 | ```php 552 | function parseBetterJSAlternative(string $code): void 553 | { 554 | $regexes = [ 555 | // ... 556 | ]; 557 | 558 | $statements = explode(' ', $code); 559 | $tokens = []; 560 | foreach ($regexes as $regex) { 561 | foreach ($statements as $statement) { 562 | // ... 563 | } 564 | } 565 | 566 | $ast = []; 567 | foreach ($tokens as $token) { 568 | // lex... 569 | } 570 | 571 | foreach ($ast as $node) { 572 | // parse... 573 | } 574 | } 575 | ``` 576 | 577 | **여전히 나쁜 예:** 578 | 579 | 기능의 일부를 옮겼지만, `parseBetterJSAlternative()` 함수는 여전히 너무 복잡하고 테스트할 수 없습니다. 580 | 581 | ```php 582 | function tokenize(string $code): array 583 | { 584 | $regexes = [ 585 | // ... 586 | ]; 587 | 588 | $statements = explode(' ', $code); 589 | $tokens = []; 590 | foreach ($regexes as $regex) { 591 | foreach ($statements as $statement) { 592 | $tokens[] = /* ... */; 593 | } 594 | } 595 | 596 | return $tokens; 597 | } 598 | 599 | function lexer(array $tokens): array 600 | { 601 | $ast = []; 602 | foreach ($tokens as $token) { 603 | $ast[] = /* ... */; 604 | } 605 | 606 | return $ast; 607 | } 608 | 609 | function parseBetterJSAlternative(string $code): void 610 | { 611 | $tokens = tokenize($code); 612 | $ast = lexer($tokens); 613 | foreach ($ast as $node) { 614 | // parse... 615 | } 616 | } 617 | ``` 618 | 619 | **좋은 예:** 620 | 621 | 가장 좋은 해결책은 `parseBetterJSAlternative()` 함수의 의존성을 제거하는 것 입니다. 622 | 623 | ```php 624 | class Tokenizer 625 | { 626 | public function tokenize(string $code): array 627 | { 628 | $regexes = [ 629 | // ... 630 | ]; 631 | 632 | $statements = explode(' ', $code); 633 | $tokens = []; 634 | foreach ($regexes as $regex) { 635 | foreach ($statements as $statement) { 636 | $tokens[] = /* ... */; 637 | } 638 | } 639 | 640 | return $tokens; 641 | } 642 | } 643 | 644 | class Lexer 645 | { 646 | public function lexify(array $tokens): array 647 | { 648 | $ast = []; 649 | foreach ($tokens as $token) { 650 | $ast[] = /* ... */; 651 | } 652 | 653 | return $ast; 654 | } 655 | } 656 | 657 | class BetterJSAlternative 658 | { 659 | private $tokenizer; 660 | private $lexer; 661 | 662 | public function __construct(Tokenizer $tokenizer, Lexer $lexer) 663 | { 664 | $this->tokenizer = $tokenizer; 665 | $this->lexer = $lexer; 666 | } 667 | 668 | public function parse(string $code): void 669 | { 670 | $tokens = $this->tokenizer->tokenize($code); 671 | $ast = $this->lexer->lexify($tokens); 672 | foreach ($ast as $node) { 673 | // parse... 674 | } 675 | } 676 | } 677 | ``` 678 | 679 | **[⬆ 위로 가기](#목차)** 680 | 681 | ### 플래그를 함수의 매개변수로 사용하지 마세요 682 | 683 | 플래그를 사용하는 것은 함수가 한가지 이상의 일을 할 것이라 말하는 셈입니다. 함수는 한 가지 일을 해야 합니다. 684 | 만약 Boolean 값에 따라 서로 다른 코드를 사용해야 한다면 함수를 분리하세요. 685 | 686 | **나쁜 예:** 687 | 688 | ```php 689 | function createFile(string $name, bool $temp = false): void 690 | { 691 | if ($temp) { 692 | touch('./temp/'.$name); 693 | } else { 694 | touch($name); 695 | } 696 | } 697 | ``` 698 | 699 | **좋은 예:** 700 | 701 | ```php 702 | function createFile(string $name): void 703 | { 704 | touch($name); 705 | } 706 | 707 | function createTempFile(string $name): void 708 | { 709 | touch('./temp/'.$name); 710 | } 711 | ``` 712 | 713 | **[⬆ 위로 가기](#목차)** 714 | 715 | ### 부작용을 피하세요 716 | 717 | 함수는 다른 값을 가져와서 반환하는 것 외에 다른 기능을 수행하는 경우 부작용을 낳습니다. 718 | 부작용은 파일에 쓰는 것일 수도 있고, 몇몇 전역 변수의 값을 수정하는 것, 실수로 모든 돈을 낯선 사람에게 보내는 것이 될 수 있습니다. 719 | 720 | 때때로 프로그램에서 부작용을 가질 필요가 있습니다. 앞의 예와 같이, 한 파일에 쓸 필요가 있을 수 있습니다. 721 | 우리가 하고 싶은 것은 이 일을 하는 곳을 모으는 것입니다. 특정 파일에 쓰기 위한 여러 개의 함수와 클래스를 갖지 마세요. 722 | 그 역할을 하는 하나의 서비스만 가지세요. 하나, 단 하나만요. 723 | 724 | 요점은 일반적인 함정을 피하라는 것입니다. 구조가 없는 객체 간에 상태를 공유하는 것이나, 어떤 것으로도 쓰일 수 있는 변동성 데이터를 사용하는 것, 부작용이 발생할 수 있는 곳을 모으지 않는 것 같은 함정에서요. 725 | 만약 우리가 함정을 피할 수 있다면, 다른 대부분의 프로그래머보다 더 행복해질 거예요. 726 | 727 | **나쁜 예:** 728 | 729 | ```php 730 | // 다음 함수에서 참조하는 전역 변수 731 | // 이 이름을 사용하는 다른 함수가 있다면, 이제 배열이 되었을거고 함수가 작동하지 않을 수 있습니다. 732 | $name = 'Ryan McDermott'; 733 | 734 | function splitIntoFirstAndLastName(): void 735 | { 736 | global $name; 737 | 738 | $name = explode(' ', $name); 739 | } 740 | 741 | splitIntoFirstAndLastName(); 742 | 743 | var_dump($name); // ['Ryan', 'McDermott']; 744 | ``` 745 | 746 | **좋은 예:** 747 | 748 | ```php 749 | function splitIntoFirstAndLastName(string $name): array 750 | { 751 | return explode(' ', $name); 752 | } 753 | 754 | $name = 'Ryan McDermott'; 755 | $newName = splitIntoFirstAndLastName($name); 756 | 757 | var_dump($name); // 'Ryan McDermott'; 758 | var_dump($newName); // ['Ryan', 'McDermott']; 759 | ``` 760 | 761 | **[⬆ 위로 가기](#목차)** 762 | 763 | ### 전역 함수를 사용하지 마세요 764 | 765 | 전역을 오염시키는 것은 여러 언어에서 나쁜 관행입니다. 왜냐하면 다른 라이브러리와 충돌 할 수 있고 API 사용자는 상용에서 예외를 받을 때까지 종잡을 수 없기 때문이죠. 766 | 예를 들어볼까요? 만약 설정 배열을 갖고싶다면 어떨까요? `config()`같은 전역 함수를 작성할 수 있지만, 같은 작업을 시도하려는 다른 라이브러리와 충돌 할 수 있습니다. 767 | 768 | **나쁜 예:** 769 | 770 | ```php 771 | function config(): array 772 | { 773 | return [ 774 | 'foo' => 'bar', 775 | ] 776 | } 777 | ``` 778 | 779 | **좋은 예:** 780 | 781 | ```php 782 | class Configuration 783 | { 784 | private $configuration = []; 785 | 786 | public function __construct(array $configuration) 787 | { 788 | $this->configuration = $configuration; 789 | } 790 | 791 | public function get(string $key): ?string 792 | { 793 | return isset($this->configuration[$key]) ? $this->configuration[$key] : null; 794 | } 795 | } 796 | ``` 797 | 798 | 설정을 불러오고 `Configuration` 클래스의 인스턴스를 생성하세요. 799 | 800 | ```php 801 | $configuration = new Configuration([ 802 | 'foo' => 'bar', 803 | ]); 804 | ``` 805 | 이제 우리는 애플리케이션에서 `Configuration`의 인스턴스를 반드시 사용해야 합니다. 806 | 807 | **[⬆ 위로 가기](#목차)** 808 | 809 | ### 싱글턴 패턴을 사용하지 마세요 810 | 811 | 싱글턴은 [안티 패턴](https://en.wikipedia.org/wiki/Singleton_pattern) 입니다. 812 | 813 | Brian Button이 쉽게 풀어쓴 바로는 814 | 1. 싱글턴은 일반적으로 **전역 인스턴스** 로 사용되는데, 왜 그게 그렇게 나쁠까요? 그 이유는 애플리케이션의 **의존성** 을 인터페이스를 통해 노출하는 대신 코드에 **숨기기** 때문입니다. 넘기는 것을 피하기 위해 무언가를 전역으로 만드는 것은 [코드 스멜](https://en.wikipedia.org/wiki/Code_smell)입니다. 815 | 2. **스스로의 생성과 생명주기를 제어** 한다는 사실에 의해서 [단일 책임 원칙](#단일-책임-원칙-srp)를 어깁니다. 816 | 3. 본질적으로 코드를 단단하게 [결합](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29)시키는 원인이 됩니다. 이로인해 많은 경우에서 **테스트가 어려워지게** 만듭니다. 817 | 4. 애플리케이션의 생명주기 동안 상태를 유지합니다. 유닛 테스트에서는 크게 문제가 되지 않지만 **테스트가 요구되는 상황으로 끝날 수 있어** 또 다른 타격이 발생할 수 있습니다. 그 이유는 무엇일까요? 각각의 유닛 테스트는 다른 유닛 테스트와 독립적이어야 하기 때문입니다. 818 | 819 | [Misko Hevery](http://misko.hevery.com/about/)가 [문제의 근원](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/)에 대해 작성한 매우 좋은 생각도 있습니다. 820 | 821 | **나쁜 예:** 822 | 823 | ```php 824 | class DBConnection 825 | { 826 | private static $instance; 827 | 828 | private function __construct(string $dsn) 829 | { 830 | // ... 831 | } 832 | 833 | public static function getInstance(): DBConnection 834 | { 835 | if (self::$instance === null) { 836 | self::$instance = new self(); 837 | } 838 | 839 | return self::$instance; 840 | } 841 | 842 | // ... 843 | } 844 | 845 | $singleton = DBConnection::getInstance(); 846 | ``` 847 | 848 | **좋은 예:** 849 | 850 | ```php 851 | class DBConnection 852 | { 853 | public function __construct(string $dsn) 854 | { 855 | // ... 856 | } 857 | 858 | // ... 859 | } 860 | ``` 861 | 862 | `DBConnection` 클래스의 인스턴스를 생성하고 [DSN](http://php.net/manual/en/pdo.construct.php#refsect1-pdo.construct-parameters)을 설정합니다. 863 | 864 | ```php 865 | $connection = new DBConnection($dsn); 866 | ``` 867 | 이제 우리는 어플리케이션에서 `DBConnection`의 인스턴스를 사용해야 합니다. 868 | 869 | **[⬆ 위로 가기](#목차)** 870 | 871 | ### 조건문은 캡슐화하세요 872 | 873 | **나쁜 예:** 874 | 875 | ```php 876 | if ($article->state === 'published') { 877 | // ... 878 | } 879 | ``` 880 | 881 | **좋은 예:** 882 | 883 | ```php 884 | if ($article->isPublished()) { 885 | // ... 886 | } 887 | ``` 888 | 889 | **[⬆ 위로 가기](#목차)** 890 | 891 | ### 부정 조건문을 피하세요 892 | 893 | **나쁜 예:** 894 | 895 | ```php 896 | function isDOMNodeNotPresent(\DOMNode $node): bool 897 | { 898 | // ... 899 | } 900 | 901 | if (!isDOMNodeNotPresent($node)) 902 | { 903 | // ... 904 | } 905 | ``` 906 | 907 | **좋은 예:** 908 | 909 | ```php 910 | function isDOMNodePresent(\DOMNode $node): bool 911 | { 912 | // ... 913 | } 914 | 915 | if (isDOMNodePresent($node)) { 916 | // ... 917 | } 918 | ``` 919 | 920 | **[⬆ 위로 가기](#목차)** 921 | 922 | ### 조건문을 피하세요 923 | 924 | 이건 불가능한 일처럼 보일 겁니다. 이 말을 처음 들은 대부분의 사람들은 묻습니다. "어떻게 `if` 문 없이 뭔가를 할 수 있죠?" 925 | 그에 대한 답은 다형성입니다. 많은 케이스에서 같은 일을 완수하기 위해 우리는 다형성을 사용할 수 있습니다. 926 | 두 번째 질문은 일반적으로 이럴 거예요. "음, 좋습니다. 하지만 왜 제가 그렇게 해야 하죠?" 927 | 그에 대한 대답은 우리가 이전에 배운 클린코드 개념 때문입니다. '함수는 한 가지만 해야 합니다.' 928 | `if` 문을 가진 함수와 클래스를 가지게 될 때, 우리는 우리의 함수가 한가지 이상을 한다고 사람들에게 말하는 셈입니다. 929 | 기억하세요. 딱 한 가지만 하세요. 930 | 931 | **나쁜 예:** 932 | 933 | ```php 934 | class Airplane 935 | { 936 | // ... 937 | 938 | public function getCruisingAltitude(): int 939 | { 940 | switch ($this->type) { 941 | case '777': 942 | return $this->getMaxAltitude() - $this->getPassengerCount(); 943 | case 'Air Force One': 944 | return $this->getMaxAltitude(); 945 | case 'Cessna': 946 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 947 | } 948 | } 949 | } 950 | ``` 951 | 952 | **좋은 예:** 953 | 954 | ```php 955 | interface Airplane 956 | { 957 | // ... 958 | 959 | public function getCruisingAltitude(): int; 960 | } 961 | 962 | class Boeing777 implements Airplane 963 | { 964 | // ... 965 | 966 | public function getCruisingAltitude(): int 967 | { 968 | return $this->getMaxAltitude() - $this->getPassengerCount(); 969 | } 970 | } 971 | 972 | class AirForceOne implements Airplane 973 | { 974 | // ... 975 | 976 | public function getCruisingAltitude(): int 977 | { 978 | return $this->getMaxAltitude(); 979 | } 980 | } 981 | 982 | class Cessna implements Airplane 983 | { 984 | // ... 985 | 986 | public function getCruisingAltitude(): int 987 | { 988 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 989 | } 990 | } 991 | ``` 992 | 993 | **[⬆ 위로 가기](#목차)** 994 | 995 | ### 타입 체킹을 피하세요 (part 1) 996 | 997 | PHP는 타입이 없습니다. 즉, 우리의 함수가 어떠한 타입의 인수도 가질 수 있다는 걸 뜻합니다. 998 | 때때로 우리는 이러한 자유에 물리기도 합니다. 그래서 우리의 함수 내에서 타입 체킹을 하는 것에 구미가 당기게 되곤 합니다. 999 | 이렇게 하는 것을 피하는 많은 방법이 있습니다. 고려할 첫 번째는 일관된 API입니다. 1000 | 1001 | **나쁜 예:** 1002 | 1003 | ```php 1004 | function travelToTexas($vehicle): void 1005 | { 1006 | if ($vehicle instanceof Bicycle) { 1007 | $vehicle->pedalTo(new Location('texas')); 1008 | } elseif ($vehicle instanceof Car) { 1009 | $vehicle->driveTo(new Location('texas')); 1010 | } 1011 | } 1012 | ``` 1013 | 1014 | **좋은 예:** 1015 | 1016 | ```php 1017 | function travelToTexas(Traveler $vehicle): void 1018 | { 1019 | $vehicle->travelTo(new Location('texas')); 1020 | } 1021 | ``` 1022 | 1023 | **[⬆ 위로 가기](#목차)** 1024 | 1025 | ### 타입 체킹을 피하세요 (part 2) 1026 | 1027 | 만약 PHP 7 이상의 버전으로 string, integers, 그리고 array 같은 기본 자료형을 가지고 일하고 있고, 다형성을 사용할 순 없지만 1028 | 여전히 타입을 체크해야할 필요성을 느낀다면 [타입 선언](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration)이나 strict 모드를 고려해야 합니다. 1029 | 이것은 표준 PHP 문법에 정적 타입을 제공합니다. 1030 | 직접 타입을 체크할 때의 문제는, 너무 많은 추가적인 장황함을 필요로 해서 잃어버린 가독성을 보상해줄 수 없는 가짜 "안전한 타입"을 만들어 낸다는 것입니다. 1031 | PHP는 깔끔하게 두고, 좋은 테스트를 작성하고, 좋은 코드 리뷰를 가지세요. 그게 싫다면, PHP strict 타입 선언이나 strict 모드로 모든 작업을 수행하세요. 1032 | 1033 | **나쁜 예:** 1034 | 1035 | ```php 1036 | function combine($val1, $val2): int 1037 | { 1038 | if (!is_numeric($val1) || !is_numeric($val2)) { 1039 | throw new \Exception('숫자 타입이어야 합니다.'); 1040 | } 1041 | 1042 | return $val1 + $val2; 1043 | } 1044 | ``` 1045 | 1046 | **좋은 예:** 1047 | 1048 | ```php 1049 | function combine(int $val1, int $val2): int 1050 | { 1051 | return $val1 + $val2; 1052 | } 1053 | ``` 1054 | 1055 | **[⬆ 위로 가기](#목차)** 1056 | 1057 | ### 불필요한 코드는 제거하세요 1058 | 1059 | 불필요한 코드는 중복된 코드만큼이나 나쁩니다. 코드에 남겨야 할 이유가 전혀 없습니다. 호출이 안되고 있다면 없애버리세요! 1060 | 만약 여전히 필요하대도 버전 기록에 안전하게 남아있을 것입니다. 1061 | 1062 | **나쁜 예:** 1063 | 1064 | ```php 1065 | function oldRequestModule(string $url): void 1066 | { 1067 | // ... 1068 | } 1069 | 1070 | function newRequestModule(string $url): void 1071 | { 1072 | // ... 1073 | } 1074 | 1075 | $request = newRequestModule($requestUrl); 1076 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1077 | ``` 1078 | 1079 | **좋은 예:** 1080 | 1081 | ```php 1082 | function requestModule(string $url): void 1083 | { 1084 | // ... 1085 | } 1086 | 1087 | $request = requestModule($requestUrl); 1088 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1089 | ``` 1090 | 1091 | **[⬆ 위로 가기](#목차)** 1092 | 1093 | 1094 | ## 객체와 자료구조 1095 | 1096 | ### 객체 캡슐화를 사용하세요 1097 | 1098 | PHP에서는 메소드에 `public`, `protected`,`private`을 설정할 수 있습니다. 1099 | 이를 통해 객체의 프로퍼티 수정을 제어할 수 있습니다. 1100 | 1101 | * 객체 프로퍼티를 얻는 것 이상의 일을 하고 싶다면, 코드단에 있는 모든 접근자를 찾아보고 바꿀 필요가 없습니다. 1102 | * `set`을 할때 검증(밸리데이션)을 간단하게 추가합니다. 1103 | * 내부 표현을 캡슐화합니다. 1104 | * 가져오고 설정할 때 로깅 및 오류 처리를 추가하기 쉽습니다. 1105 | * 클래스를 상속 받아서, 기본 기능을 오버라이드 할 수 있습니다. 1106 | * 서버에서 가져온다고 할 때, 객체의 프로퍼티를 느리게 로드할 수 있습니다. 1107 | 1108 | 또한, 이는 [개방/폐쇄 원칙 (OCP)](#개방폐쇄-원칙-ocp) 원칙의 일부입니다. 1109 | 1110 | **나쁜 예:** 1111 | 1112 | ```php 1113 | class BankAccount 1114 | { 1115 | public $balance = 1000; 1116 | } 1117 | 1118 | $bankAccount = new BankAccount(); 1119 | 1120 | // 신발을 삼 1121 | $bankAccount->balance -= 100; 1122 | ``` 1123 | 1124 | **좋은 예:** 1125 | 1126 | ```php 1127 | class BankAccount 1128 | { 1129 | private $balance; 1130 | 1131 | public function __construct(int $balance = 1000) 1132 | { 1133 | $this->balance = $balance; 1134 | } 1135 | 1136 | public function withdraw(int $amount): void 1137 | { 1138 | if ($amount > $this->balance) { 1139 | throw new \Exception('Amount greater than available balance.'); 1140 | } 1141 | 1142 | $this->balance -= $amount; 1143 | } 1144 | 1145 | public function deposit(int $amount): void 1146 | { 1147 | $this->balance += $amount; 1148 | } 1149 | 1150 |    public function getBalance(): int 1151 | { 1152 | return $this->balance; 1153 | } 1154 | } 1155 | 1156 | $bankAccount = new BankAccount(); 1157 | 1158 | // 신발을 삼 1159 | $bankAccount->withdraw($shoesPrice); 1160 | 1161 | // 잔액을 받아옴 1162 | $balance = $bankAccount->getBalance(); 1163 | ``` 1164 | 1165 | **[⬆ 위로 가기](#목차)** 1166 | 1167 | ### 객체가 private/protected 멤버를 갖게 하세요 1168 | 1169 | * `public`메소드와 프로퍼티는 변경에 가장 취약합니다. 그 이유는 어떤 외부 코드가 쉽게 의존할 수 있고, 어떤 코드가 의존하고 있는지 제어할 수 없기 때문입니다. 1170 | **클래스의 수정은 클래스의 모든 사용자에게 위험합니다.** 1171 | * `protected` 제어자는 `public` 만큼이나 위험합니다. 자식 클래스 범위내에서 사용할 수 있기 때문입니다. 이는 public과 protected의 차이점은 접근 매커니즘에만 있다는 것을 의미하나, 캡슐화 보증은 동일하게 유지됩니다. **클래스의 수정은 모든 하위 클래스에 위험합니다.** 1172 | * `private` 제어자는 코드가 **단일 클래스의 경계에서만 수정하는 것이 위험함** 을 보증합니다(변경하는 것이 안전하며 [젠가 효과](http://www.urbandictionary.com/define.php?term=Jengaphobia&defid=2494196)를 갖지 않을 것 입니다.). 1173 | 1174 | 그러므로 `private` 을 기본으로 사용하고 외부 클래스에 대한 접근 권한을 제공해야 할 때 `public/protected`를 사용하세요. 1175 | 1176 | 더 많은 정보를 원하면 이 주제에 대해서 [Fabien Potencier](https://github.com/fabpot)가 작성한 [블로그 포스트](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html)를 읽어볼 수 있습니다. 1177 | 1178 | **나쁜 예:** 1179 | 1180 | ```php 1181 | class Employee 1182 | { 1183 | public $name; 1184 | 1185 | public function __construct(string $name) 1186 | { 1187 | $this->name = $name; 1188 | } 1189 | } 1190 | 1191 | $employee = new Employee('John Doe'); 1192 | echo 'Employee name: '.$employee->name; // 직원명: John Doe 1193 | ``` 1194 | 1195 | **좋은 예:** 1196 | 1197 | ```php 1198 | class Employee 1199 | { 1200 | private $name; 1201 | 1202 | public function __construct(string $name) 1203 | { 1204 | $this->name = $name; 1205 | } 1206 | 1207 | public function getName(): string 1208 | { 1209 | return $this->name; 1210 | } 1211 | } 1212 | 1213 | $employee = new Employee('John Doe'); 1214 | echo 'Employee name: '.$employee->getName(); // 직원명: John Doe 1215 | ``` 1216 | 1217 | **[⬆ 위로 가기](#목차)** 1218 | 1219 | ## 클래스 1220 | 1221 | ### 상속보다는 컴포지션을 사용하세요 1222 | 1223 | GoF(the Gang of Four)의 [*디자인 패턴*](https://en.wikipedia.org/wiki/Design_Patterns)에서 잘 알려진 바와 같이, 1224 | 가능하다면 상속보다는 컴포지션을 선호해야 합니다. 1225 | 상속을 사용하는 데는 여러 가지 좋은 이유가 있으며 컴포지션을 사용하는 데도 여러 가지 좋은 이유가 있습니다. 1226 | 1227 | 이 좌우명의 요지는 만약 우리가 본능적으로 상속을 생각한다면, 컴포지션이 우리의 문제를 더 잘 설계할 수 있을지 생각해보려 노력하라는 것입니다. 1228 | 어떤 경우에는 그렇거든요. 1229 | 1230 | 아마도 이렇게 생각하고 있을지도 모릅니다, "그럼 언제 상속을 사용해야 하지?" 눈앞에 닥친 문제에 따라 다릅니다만, 아래 목록은 상속이 컴포지션보다 더 잘 맞는 경우입니다. 1231 | 1232 | 1. 상속이 "has-a" 관계가 아닌 "is-a"관계를 나타냅니다. (사용자->사용자 세부정보 vs 인간->동물) 1233 | 2. 기본 클래스의 코드를 재사용할 수 있습니다. (인간은 모든 동물처럼 움직일 수 있습니다.) 1234 | 3. 기본 클래스를 변경하여 파생 클래스에 대한 전역 변경을 원하는 경우 (모든 동물의 움직일 때의 칼로리 소모량 변경) 1235 | 1236 | **나쁜 예:** 1237 | 1238 | ```php 1239 | class Employee 1240 | { 1241 | private $name; 1242 | private $email; 1243 | 1244 | public function __construct(string $name, string $email) 1245 | { 1246 | $this->name = $name; 1247 | $this->email = $email; 1248 | } 1249 | 1250 | // ... 1251 | } 1252 | 1253 | // 직원(Employee)들이 세금 데이터를 "가지고" 있기 때문에 좋지 않습니다. 1254 | // EmployeeTaxData는 직원(Employee) 타입이 아닙니다. 1255 | 1256 | class EmployeeTaxData extends Employee 1257 | { 1258 | private $ssn; 1259 | private $salary; 1260 | 1261 | public function __construct(string $name, string $email, string $ssn, string $salary) 1262 | { 1263 | parent::__construct($name, $email); 1264 | 1265 | $this->ssn = $ssn; 1266 | $this->salary = $salary; 1267 | } 1268 | 1269 | // ... 1270 | } 1271 | ``` 1272 | 1273 | **좋은 예:** 1274 | 1275 | ```php 1276 | class EmployeeTaxData 1277 | { 1278 | private $ssn; 1279 | private $salary; 1280 | 1281 | public function __construct(string $ssn, string $salary) 1282 | { 1283 | $this->ssn = $ssn; 1284 | $this->salary = $salary; 1285 | } 1286 | 1287 | // ... 1288 | } 1289 | 1290 | class Employee 1291 | { 1292 | private $name; 1293 | private $email; 1294 | private $taxData; 1295 | 1296 | public function __construct(string $name, string $email) 1297 | { 1298 | $this->name = $name; 1299 | $this->email = $email; 1300 | } 1301 | 1302 | public function setTaxData(string $ssn, string $salary) 1303 | { 1304 | $this->taxData = new EmployeeTaxData($ssn, $salary); 1305 | } 1306 | 1307 | // ... 1308 | } 1309 | ``` 1310 | 1311 | **[⬆ 위로 가기](#목차)** 1312 | 1313 | ### 유창한 인터페이스를 피하세요 1314 | 1315 | [유창한 인터페이스](https://en.wikipedia.org/wiki/Fluent_interface)는 [Method chaining](https://en.wikipedia.org/wiki/Method_chaining)을 사용해 소스 코드의 가독성을 증진시키기 위한 객체지향 API입니다. 1316 | 1317 | 어떤 맥락이 있을 수 있지만, 패턴들이 코드의 장황함을 줄여주는, 주로 빌더 객체들은(예를 들어 [PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html) 나 [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)) 조금 더 자주 비용을 동반하곤 합니다. 1318 | 1319 | 1. [캡슐화](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29)를 어깁니다. 1320 | 2. [데코레이터 패턴](https://en.wikipedia.org/wiki/Decorator_pattern)을 어깁니다. 1321 | 3. 테스트 슈트에서 [mock](https://en.wikipedia.org/wiki/Mock_object)이 어려워집니다. 1322 | 4. commit의 변경사항을 읽기 어렵게 만듭니다. 1323 | 1324 | 더 많은 정보는 [Marco Pivetta](https://github.com/Ocramius)가 이 주제에 대해 작성한 [blog post](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) 에서 전체를 읽어 볼 수 있습니다. 1325 | 1326 | **나쁜 예:** 1327 | 1328 | ```php 1329 | class Car 1330 | { 1331 | private $make = 'Honda'; 1332 | private $model = 'Accord'; 1333 | private $color = 'white'; 1334 | 1335 | public function setMake(string $make): self 1336 | { 1337 | $this->make = $make; 1338 | 1339 | // NOTE: 체인에 대한 반환 1340 | return $this; 1341 | } 1342 | 1343 | public function setModel(string $model): self 1344 | { 1345 | $this->model = $model; 1346 | 1347 | // NOTE: 체인에 대한 반환 1348 | return $this; 1349 | } 1350 | 1351 | public function setColor(string $color): self 1352 | { 1353 | $this->color = $color; 1354 | 1355 | // NOTE: 체인에 대한 반환 1356 | return $this; 1357 | } 1358 | 1359 | public function dump(): void 1360 | { 1361 | var_dump($this->make, $this->model, $this->color); 1362 | } 1363 | } 1364 | 1365 | $car = (new Car()) 1366 | ->setColor('pink') 1367 | ->setMake('Ford') 1368 | ->setModel('F-150') 1369 | ->dump(); 1370 | ``` 1371 | 1372 | **좋은 예:** 1373 | 1374 | ```php 1375 | class Car 1376 | { 1377 | private $make = 'Honda'; 1378 | private $model = 'Accord'; 1379 | private $color = 'white'; 1380 | 1381 | public function setMake(string $make): void 1382 | { 1383 | $this->make = $make; 1384 | } 1385 | 1386 | public function setModel(string $model): void 1387 | { 1388 | $this->model = $model; 1389 | } 1390 | 1391 | public function setColor(string $color): void 1392 | { 1393 | $this->color = $color; 1394 | } 1395 | 1396 | public function dump(): void 1397 | { 1398 | var_dump($this->make, $this->model, $this->color); 1399 | } 1400 | } 1401 | 1402 | $car = new Car(); 1403 | $car->setColor('pink'); 1404 | $car->setMake('Ford'); 1405 | $car->setModel('F-150'); 1406 | $car->dump(); 1407 | ``` 1408 | 1409 | **[⬆ 위로 가기](#목차)** 1410 | 1411 | ## SOLID 1412 | 1413 | **SOLID** 는 Robert Martin이 명명한 5가지 원칙의 앞 글자로 객체지향 프로그래밍과 디자인을 뜻하는 5가지 기본 원칙을 뜻합니다. Michael Feathers가 고안한 니모닉 약어를 사용하였습니다. 1414 | 1415 | * [S: 단일 책임 원칙 (SRP)](#단일-책임-원칙-srp) 1416 | * [O: 개방/폐쇄 원칙 (OCP)](#개방폐쇄-원칙-ocp) 1417 | * [L: 리스코브 치환 원칙 (LSP)](#리스코브-치환-원칙-lsp) 1418 | * [I: 인터페이스 분리 원칙 (ISP)](#인터페이스-분리-원칙-isp) 1419 | * [D: 의존성 역전 원칙 (DIP)](#의존성-역전-원칙-dip) 1420 | 1421 | ### 단일 책임 원칙 (SRP) 1422 | 1423 | 클린 코드에서 명시되어 있듯이, "클래스를 변경하는데 한가지 이상의 이유가 있어서는 안 됩니다." 1424 | 한 클래스를 많은 기능을 꽉 채우는 것은 유혹적입니다. 마치 기내에 딱 하나의 여행용 가방을 가져갈 수 있을 때처럼요. 1425 | 이것의 문제는 클래스가 개념적으로 일관되지 않게되며 클래스를 변경할 여러 이유를 만들 것이라는 겁니다. 1426 | 클래스를 변경해야 할 필요가 있을 때 드는 시간을 최소화시키는 것은 중요합니다. 그 이유는 1427 | 만약, 너무 많은 기능이 한 클래스에 있고 그중 한 부분을 수정해야 한다면 코드 안에 의존하고 있는 다른 모듈에 어떤 영향을 미치는지 파악하기 어렵기 때문입니다. 1428 | 1429 | **나쁜 예:** 1430 | 1431 | ```php 1432 | class UserSettings 1433 | { 1434 | private $user; 1435 | 1436 | public function __construct(User $user) 1437 | { 1438 | $this->user = $user; 1439 | } 1440 | 1441 | public function changeSettings(array $settings): void 1442 | { 1443 | if ($this->verifyCredentials()) { 1444 | // ... 1445 | } 1446 | } 1447 | 1448 | private function verifyCredentials(): bool 1449 | { 1450 | // ... 1451 | } 1452 | } 1453 | ``` 1454 | 1455 | **좋은 예:** 1456 | 1457 | ```php 1458 | class UserAuth 1459 | { 1460 | private $user; 1461 | 1462 | public function __construct(User $user) 1463 | { 1464 | $this->user = $user; 1465 | } 1466 | 1467 | public function verifyCredentials(): bool 1468 | { 1469 | // ... 1470 | } 1471 | } 1472 | 1473 | class UserSettings 1474 | { 1475 | private $user; 1476 | private $auth; 1477 | 1478 | public function __construct(User $user) 1479 | { 1480 | $this->user = $user; 1481 | $this->auth = new UserAuth($user); 1482 | } 1483 | 1484 | public function changeSettings(array $settings): void 1485 | { 1486 | if ($this->auth->verifyCredentials()) { 1487 | // ... 1488 | } 1489 | } 1490 | } 1491 | ``` 1492 | 1493 | **[⬆ 위로 가기](#목차)** 1494 | 1495 | ### 개방/폐쇄 원칙 (OCP) 1496 | 1497 | Bertrand Meyer가 말하기를, "소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해서는 개방되어야 하며, 변경에서는 폐쇄되어 있어야 합니다." 1498 | 이것은 무엇을 의미할까요? 이 원칙은 기본적으로 사용자가 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있도록 허용해야 한다는 것을 뜻합니다. 1499 | 1500 | **나쁜 예:** 1501 | 1502 | ```php 1503 | abstract class Adapter 1504 | { 1505 | protected $name; 1506 | 1507 | public function getName(): string 1508 | { 1509 | return $this->name; 1510 | } 1511 | } 1512 | 1513 | class AjaxAdapter extends Adapter 1514 | { 1515 | public function __construct() 1516 | { 1517 | parent::__construct(); 1518 | 1519 | $this->name = 'ajaxAdapter'; 1520 | } 1521 | } 1522 | 1523 | class NodeAdapter extends Adapter 1524 | { 1525 | public function __construct() 1526 | { 1527 | parent::__construct(); 1528 | 1529 | $this->name = 'nodeAdapter'; 1530 | } 1531 | } 1532 | 1533 | class HttpRequester 1534 | { 1535 | private $adapter; 1536 | 1537 | public function __construct(Adapter $adapter) 1538 | { 1539 | $this->adapter = $adapter; 1540 | } 1541 | 1542 | public function fetch(string $url): Promise 1543 | { 1544 | $adapterName = $this->adapter->getName(); 1545 | 1546 | if ($adapterName === 'ajaxAdapter') { 1547 | return $this->makeAjaxCall($url); 1548 | } elseif ($adapterName === 'httpNodeAdapter') { 1549 | return $this->makeHttpCall($url); 1550 | } 1551 | } 1552 | 1553 | private function makeAjaxCall(string $url): Promise 1554 | { 1555 | // request하고 promise를 return함 1556 | } 1557 | 1558 | private function makeHttpCall(string $url): Promise 1559 | { 1560 | // request하고 promise를 return함 1561 | } 1562 | } 1563 | ``` 1564 | 1565 | **좋은 예:** 1566 | 1567 | ```php 1568 | interface Adapter 1569 | { 1570 | public function request(string $url): Promise; 1571 | } 1572 | 1573 | class AjaxAdapter implements Adapter 1574 | { 1575 | public function request(string $url): Promise 1576 | { 1577 | // request하고 promise를 return함 1578 | } 1579 | } 1580 | 1581 | class NodeAdapter implements Adapter 1582 | { 1583 | public function request(string $url): Promise 1584 | { 1585 | // request하고 promise를 return함 1586 | } 1587 | } 1588 | 1589 | class HttpRequester 1590 | { 1591 | private $adapter; 1592 | 1593 | public function __construct(Adapter $adapter) 1594 | { 1595 | $this->adapter = $adapter; 1596 | } 1597 | 1598 | public function fetch(string $url): Promise 1599 | { 1600 | return $this->adapter->request($url); 1601 | } 1602 | } 1603 | ``` 1604 | 1605 | **[⬆ 위로 가기](#목차)** 1606 | 1607 | ### 리스코브 치환 원칙 (LSP) 1608 | 1609 | 단순한 개념을 지칭하는 무시무시해 보이는 용어입니다. 공식적으로는 다음과 같이 정의되어있습니다. 1610 | "만약 S가 T의 서브 타입이라면, T 타입의 객체는 프로그램의 바람직한 특성(정합성, 수행된 작업 등)을 하나도 바꾸지 않으면서 S 타입의 객체로 교체될 수 있습니다. (즉, S 타입의 객체는 T 타입의 객체를 대체할 수 있습니다)" 1611 | 더 무시무시한 정의였네요. 1612 | 1613 | 이에 대한 가장 좋은 설명은 다음과 같습니다. 1614 | 만약 우리가 부모 클래스와 자식 클래스를 가지고 있다면, 부정확한 결과를 얻지 않으면서 기본 클래스와 자식 클래스를 서로 교환하여 사용할 수 있다는 것입니다. 1615 | 여전히 혼란스러울 수 있으니, 전형적인 정사각형-직사각형 예제를 들어봅시다. 1616 | 수학적으로 정사각형은 사각형입니다. 그러나, 상속을 통한 'is-a' 관계로 설계하면 빠르게 문제에 휘말리게 됩니다. 1617 | 1618 | **나쁜 예:** 1619 | 1620 | ```php 1621 | class Rectangle 1622 | { 1623 | protected $width = 0; 1624 | protected $height = 0; 1625 | 1626 | public function render(int $area): void 1627 | { 1628 | // ... 1629 | } 1630 | 1631 | public function setWidth(int $width): void 1632 | { 1633 | $this->width = $width; 1634 | } 1635 | 1636 | public function setHeight(int $height): void 1637 | { 1638 | $this->height = $height; 1639 | } 1640 | 1641 | public function getArea(): int 1642 | { 1643 | return $this->width * $this->height; 1644 | } 1645 | } 1646 | 1647 | class Square extends Rectangle 1648 | { 1649 | public function setWidth(int $width): void 1650 | { 1651 | $this->width = $this->height = $width; 1652 | } 1653 | 1654 | public function setHeight(int $height): void 1655 | { 1656 | $this->width = $this->height = $height; 1657 | } 1658 | } 1659 | 1660 | /** 1661 | * @param Rectangle[] $rectangles 1662 | */ 1663 | function renderLargeRectangles(array $rectangles): void 1664 | { 1665 | foreach ($rectangles as $rectangle) { 1666 | $rectangle->setWidth(4); 1667 | $rectangle->setHeight(5); 1668 | $area = $rectangle->getArea(); // 땡: 20이어야 하는 데, 정사각형에 25를 return할 것입니다. 1669 | $rectangle->render($area); 1670 | } 1671 | } 1672 | 1673 | $rectangles = [new Rectangle(), new Rectangle(), new Square()]; 1674 | renderLargeRectangles($rectangles); 1675 | ``` 1676 | 1677 | **좋은 예:** 1678 | 1679 | ```php 1680 | abstract class Shape 1681 | { 1682 | abstract public function getArea(): int; 1683 | 1684 | public function render(int $area): void 1685 | { 1686 | // ... 1687 | } 1688 | } 1689 | 1690 | class Rectangle extends Shape 1691 | { 1692 | private $width; 1693 | private $height; 1694 | 1695 | public function __construct(int $width, int $height) 1696 | { 1697 | $this->width = $width; 1698 | $this->height = $height; 1699 | } 1700 | 1701 | public function getArea(): int 1702 | { 1703 | return $this->width * $this->height; 1704 | } 1705 | } 1706 | 1707 | class Square extends Shape 1708 | { 1709 | private $length; 1710 | 1711 | public function __construct(int $length) 1712 | { 1713 | $this->length = $length; 1714 | } 1715 | 1716 | public function getArea(): int 1717 | { 1718 | return pow($this->length, 2); 1719 | } 1720 | } 1721 | 1722 | /** 1723 | * @param Rectangle[] $rectangles 1724 | */ 1725 | function renderLargeRectangles(array $rectangles): void 1726 | { 1727 | foreach ($rectangles as $rectangle) { 1728 | $area = $rectangle->getArea(); 1729 | $rectangle->render($area); 1730 | } 1731 | } 1732 | 1733 | $shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)]; 1734 | renderLargeRectangles($shapes); 1735 | ``` 1736 | 1737 | **[⬆ 위로 가기](#목차)** 1738 | 1739 | ### 인터페이스 분리 원칙 (ISP) 1740 | 1741 | ISP는 "클라이언트는 사용하지 않는 인터페이스에 의존하도록 강요되어서는 안 된다."를 뜻합니다. 1742 | 1743 | 이 원칙을 잘 설명해주는 예로는 많은 설정 객체를 요구하는 클래스를 들 수 있겠습니다. 1744 | 클라이언트가 많은 양의 옵션을 설치하도록 요구하지 않는 것이 이롭습니다. 대부분의 경우, 모든 설치가 필요하지는 않기 때문이죠. 1745 | 이것들을 선택사항으로 만드는 것이 "뚱뚱한 인터페이스"를 가지는 것을 방지합니다. 1746 | 1747 | **나쁜 예:** 1748 | 1749 | ```php 1750 | interface Employee 1751 | { 1752 | public function work(): void; 1753 | 1754 | public function eat(): void; 1755 | } 1756 | 1757 | class Human implements Employee 1758 | { 1759 | public function work(): void 1760 | { 1761 | // .... 일하는 중 1762 | } 1763 | 1764 | public function eat(): void 1765 | { 1766 | // ...... 점심 휴식 시간에 먹는 중 1767 | } 1768 | } 1769 | 1770 | class Robot implements Employee 1771 | { 1772 | public function work(): void 1773 | { 1774 | //.... 훨씬 많이 일하는 중 1775 | } 1776 | 1777 | public function eat(): void 1778 | { 1779 | //.... 로봇은 먹을 수 없지만, 이 메소드를 반드시 implement해야합니다.. 1780 | } 1781 | } 1782 | ``` 1783 | 1784 | **좋은 예:** 1785 | 1786 | 모든 일꾼(worker)이 직원(employee)은 아니지만, 모든 직원(employee)은 '일꾼(worker)' 입니다. 1787 | 1788 | ```php 1789 | interface Workable 1790 | { 1791 | public function work(): void; 1792 | } 1793 | 1794 | interface Feedable 1795 | { 1796 | public function eat(): void; 1797 | } 1798 | 1799 | interface Employee extends Feedable, Workable 1800 | { 1801 | } 1802 | 1803 | class Human implements Employee 1804 | { 1805 | public function work(): void 1806 | { 1807 | // .... 일하는 중 1808 | } 1809 | 1810 | public function eat(): void 1811 | { 1812 | //.... 점심 휴식시간에 먹는 중 1813 | } 1814 | } 1815 | 1816 | // 로봇은 일만 할 수 있어요 1817 | class Robot implements Workable 1818 | { 1819 | public function work(): void 1820 | { 1821 | // .... 일하는 중 1822 | } 1823 | } 1824 | ``` 1825 | 1826 | **[⬆ 위로 가기](#목차)** 1827 | 1828 | ### 의존성 역전 원칙 (DIP) 1829 | 1830 | 이 원칙은 두 가지 필수 사항을 명시합니다. 1831 | 1. 상위 레벨의 모듈은 하위 레벨의 모듈에 의존해서는 안 됩니다. 상위 레벨과 하위 레벨의 모듈 모두 추상화에 의존해야 합니다. 1832 | 2. 추상화된 것은 구체적인 것에 의존하면 안 됩니다. 구체적인 사항들은 추상화에 의존해야 합니다. 1833 | 1834 | 한 번에 이해하기는 어려울 수 있습니다. 하지만 만약 당신이 PHP 프레임워크(symfony 등)를 사용해왔다면, 의존성 주입(DI, Dependency Injection)폼 안에 해당 원칙이 구현된 것을 보아왔을 것입니다. 1835 | 같은 개념은 아니지만, DIP는 상위 레벨 모듈들이 하위 레벨 모듈의 세부 사항을 알지 못하게 하고 그것들을 설정하는 것을 막습니다. 1836 | 이것은 의존성 주입을 통해서 완수할 수 있습니다. 의존성 주입의 가장 큰 장점은 모듈 간의 결합을 줄일 수 있다는 것입니다. 1837 | 결합은 아주 안 좋은 개발 패턴입니다. 그 이유는 우리의 코드를 리팩토링하기 어렵게 만들기 때문입니다. 1838 | 1839 | **나쁜 예:** 1840 | 1841 | ```php 1842 | class Employee 1843 | { 1844 | public function work(): void 1845 | { 1846 | // ....일하는 중 1847 | } 1848 | } 1849 | 1850 | class Robot extends Employee 1851 | { 1852 | public function work(): void 1853 | { 1854 | //....훨씬 더 많이 일하는 중 1855 | } 1856 | } 1857 | 1858 | class Manager 1859 | { 1860 | private $employee; 1861 | 1862 | public function __construct(Employee $employee) 1863 | { 1864 | $this->employee = $employee; 1865 | } 1866 | 1867 | public function manage(): void 1868 | { 1869 | $this->employee->work(); 1870 | } 1871 | } 1872 | ``` 1873 | 1874 | **좋은 예:** 1875 | 1876 | ```php 1877 | interface Employee 1878 | { 1879 | public function work(): void; 1880 | } 1881 | 1882 | class Human implements Employee 1883 | { 1884 | public function work(): void 1885 | { 1886 | // ....일하는 중 1887 | } 1888 | } 1889 | 1890 | class Robot implements Employee 1891 | { 1892 | public function work(): void 1893 | { 1894 | //....훨씬 더 많이 일하는 중 1895 | } 1896 | } 1897 | 1898 | class Manager 1899 | { 1900 | private $employee; 1901 | 1902 | public function __construct(Employee $employee) 1903 | { 1904 | $this->employee = $employee; 1905 | } 1906 | 1907 | public function manage(): void 1908 | { 1909 | $this->employee->work(); 1910 | } 1911 | } 1912 | ``` 1913 | 1914 | **[⬆ 위로 가기](#목차)** 1915 | 1916 | ## 반복하지 마세요 (DRY) 1917 | 1918 | [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) 원칙을 지키도록 노력하세요. 1919 | 1920 | 중복코드를 피하고자 정말 최선을 다하세요. 중복된 코드는 나쁩니다. 만약 우리가 어떤 로직을 변경해야 할 때 고칠 곳이 한 곳 이상이라는 것을 뜻하기 때문이죠. 1921 | 1922 | 우리가 레스토랑을 운영하고 있다고 상상해보세요. 그리고 재고(토마토, 양파, 마늘, 양념 등등)를 기록하고 있다고요. 1923 | 만약 우리가 이걸 기록하는 여러 개의 목록을 가지고 있다면, 토마토가 들어간 요리를 서빙할 때마다 모든 목록을 수정해야 할 겁니다. 1924 | 만약 단 하나의 목록만 가지고 있다면, 수정할 곳은 단 한 곳뿐이겠지요! 1925 | 1926 | 종종 우리는 중복된 코드를 가집니다. 사소하게 다른 두어 개 때문인데, 많은 부분을 같이 공유하지만, 그 사소한 차이점이 1927 | 거의 같은 일을 하는 두 개 이상의 서로 다른 함수를 만들어내게 합니다. 중복된 코드를 제거하는 것은 단 하나의 함수/모듈/클래스로 서로 다른 세트를 핸들 할 수 있는 추상화를 생성하는 것을 뜻합니다. 1928 | 1929 | 올바른 추상화를 갖는 것은 중요합니다. 그것이 [클래스](#클래스) 섹션에 있는 SOLID 원칙을 따라야 하는 이유입니다. 1930 | 나쁜 추상화는 중복된 코드보다 나쁠 수 있습니다. 그러니 조심하세요! 1931 | 지금껏 말했듯이, 좋은 추상화를 만들 수 있다면 그렇게 하세요! 1932 | 같은 것을 반복 하지 마세요, 그렇지 않으면 하나를 바꾸려 할 때마다 여러 군데를 수정하고 있는 우리 자신을 발견하게 될 테니까요. 1933 | 1934 | **나쁜 예:** 1935 | 1936 | ```php 1937 | function showDeveloperList(array $developers): void 1938 | { 1939 | foreach ($developers as $developer) { 1940 | $expectedSalary = $developer->calculateExpectedSalary(); 1941 | $experience = $developer->getExperience(); 1942 | $githubLink = $developer->getGithubLink(); 1943 | $data = [ 1944 | $expectedSalary, 1945 | $experience, 1946 | $githubLink 1947 | ]; 1948 | 1949 | render($data); 1950 | } 1951 | } 1952 | 1953 | function showManagerList(array $managers): void 1954 | { 1955 | foreach ($managers as $manager) { 1956 | $expectedSalary = $manager->calculateExpectedSalary(); 1957 | $experience = $manager->getExperience(); 1958 | $githubLink = $manager->getGithubLink(); 1959 | $data = [ 1960 | $expectedSalary, 1961 | $experience, 1962 | $githubLink 1963 | ]; 1964 | 1965 | render($data); 1966 | } 1967 | } 1968 | ``` 1969 | 1970 | **좋은 예:** 1971 | 1972 | ```php 1973 | function showList(array $employees): void 1974 | { 1975 | foreach ($employees as $employee) { 1976 | $expectedSalary = $employee->calculateExpectedSalary(); 1977 | $experience = $employee->getExperience(); 1978 | $githubLink = $employee->getGithubLink(); 1979 | $data = [ 1980 | $expectedSalary, 1981 | $experience, 1982 | $githubLink 1983 | ]; 1984 | 1985 | render($data); 1986 | } 1987 | } 1988 | ``` 1989 | 1990 | **아주 좋은 예:** 1991 | 1992 | 더 간결한 코드를 사용하는게 나은 것 같군요. 1993 | 1994 | ```php 1995 | function showList(array $employees): void 1996 | { 1997 | foreach ($employees as $employee) { 1998 | render([ 1999 | $employee->calculateExpectedSalary(), 2000 | $employee->getExperience(), 2001 | $employee->getGithubLink() 2002 | ]); 2003 | } 2004 | } 2005 | ``` 2006 | 2007 | **[⬆ 위로 가기](#목차)** 2008 | 2009 | ## 번역 2010 | 2011 | 다른 언어로도 읽으실 수 있습니다. 2012 | 2013 | * :cn: **중국어:** 2014 | * [php-cpm/clean-code-php](https://github.com/php-cpm/clean-code-php) 2015 | * :ru: **러시아어:** 2016 | * [peter-gribanov/clean-code-php](https://github.com/peter-gribanov/clean-code-php) 2017 | * :es: **스페인어:** 2018 | * [fikoborquez/clean-code-php](https://github.com/fikoborquez/clean-code-php) 2019 | * :brazil: **포르투갈어:** 2020 | * [fabioars/clean-code-php](https://github.com/fabioars/clean-code-php) 2021 | * [jeanjar/clean-code-php](https://github.com/jeanjar/clean-code-php/tree/pt-br) 2022 | * :thailand: **태국어:** 2023 | * [panuwizzle/clean-code-php](https://github.com/panuwizzle/clean-code-php) 2024 | * :fr: **불어:** 2025 | * [errorname/clean-code-php](https://github.com/errorname/clean-code-php) 2026 | * 🇰🇷 **한국어:** 2027 | * [yujineeee/clean-code-php](https://github.com/yujineeee/clean-code-php) 2028 | 2029 | 2030 | **[⬆ 위로 가기](#목차)** 2031 | --------------------------------------------------------------------------------