├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tyson Maly 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Code Python 2 | 3 | ## Table of Contents 4 | 5 | 1. [Introduction](#introduction) 6 | 2. [Variables](#variables) 7 | * [Use meaningful and pronounceable variable names](#use-meaningful-and-pronounceable-variable-names) 8 | * [Use the same vocabulary for the same type of variable](#use-the-same-vocabulary-for-the-same-type-of-variable) 9 | * [Use searchable names (part 1)](#use-searchable-names-part-1) 10 | * [Use searchable names (part 2)](#use-searchable-names-part-2) 11 | * [Use explanatory variables](#use-explanatory-variables) 12 | * [Avoid nesting too deeply and return early (part 1)](#avoid-nesting-too-deeply-and-return-early-part-1) 13 | * [Avoid nesting too deeply and return early (part 2)](#avoid-nesting-too-deeply-and-return-early-part-2) 14 | * [Avoid Mental Mapping](#avoid-mental-mapping) 15 | * [Don't add unneeded context](#dont-add-unneeded-context) 16 | * [Use default arguments instead of short circuiting or conditionals](#use-default-arguments-instead-of-short-circuiting-or-conditionals) 17 | 3. [Comparison](#comparison) 18 | * [Use identical comparison](#use-identical-comparison) 19 | 4. [Functions](#functions) 20 | * [Function arguments (2 or fewer ideally)](#function-arguments-2-or-fewer-ideally) 21 | * [Functions should do one thing](#functions-should-do-one-thing) 22 | * [Function names should say what they do](#function-names-should-say-what-they-do) 23 | * [Functions should only be one level of abstraction](#functions-should-only-be-one-level-of-abstraction) 24 | * [Don't use flags as function parameters](#dont-use-flags-as-function-parameters) 25 | * [Avoid Side Effects](#avoid-side-effects) 26 | * [Don't write to global functions](#dont-write-to-global-functions) 27 | * [Don't use a Singleton pattern](#dont-use-a-singleton-pattern) 28 | * [Encapsulate conditionals](#encapsulate-conditionals) 29 | * [Avoid negative conditionals](#avoid-negative-conditionals) 30 | * [Avoid conditionals](#avoid-conditionals) 31 | * [Avoid type-checking (part 1)](#avoid-type-checking-part-1) 32 | * [Avoid type-checking (part 2)](#avoid-type-checking-part-2) 33 | * [Remove dead code](#remove-dead-code) 34 | 5. [Objects and Data Structures](#objects-and-data-structures) 35 | * [Use object encapsulation](#use-object-encapsulation) 36 | * [Make objects have private/protected members](#make-objects-have-privateprotected-members) 37 | 6. [Classes](#classes) 38 | * [Prefer composition over inheritance](#prefer-composition-over-inheritance) 39 | * [Avoid fluent interfaces](#avoid-fluent-interfaces) 40 | * [Prefer `final` classes](#prefer-final-classes) 41 | 7. [SOLID](#solid) 42 | * [Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) 43 | * [Open/Closed Principle (OCP)](#openclosed-principle-ocp) 44 | * [Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) 45 | * [Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) 46 | * [Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) 47 | 8. [Don’t repeat yourself (DRY)](#dont-repeat-yourself-dry) 48 | 9. [Translations](#translations) 49 | 50 | ## Introduction 51 | 52 | Software engineering principles, from Robert C. Martin's book 53 | [*Clean Code*](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882), 54 | adapted for Python. This is not a style guide. It's a guide to producing 55 | readable, reusable, and refactorable software in Python. 56 | 57 | Not every principle herein has to be strictly followed, and even fewer will be universally 58 | agreed upon. These are guidelines and nothing more, but they are ones codified over many 59 | years of collective experience by the authors of *Clean Code*. 60 | 61 | Inspired from [clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript). 62 | 63 | Most examples in this article will work with Python 2.x and Python 3.x 64 | 65 | Status: In Progress 66 | 67 | ## Variables 68 | 69 | ### Use meaningful and pronounceable variable names 70 | 71 | **Bad:** 72 | 73 | ```python 74 | from datetime import date 75 | 76 | ymdstr = date.now().isoformat() 77 | ``` 78 | 79 | **Good:** 80 | 81 | ```python 82 | from datetime import date 83 | 84 | currentDate = date.now().isoformat() 85 | ``` 86 | 87 | **[⬆ back to top](#table-of-contents)** 88 | 89 | ### Use the same vocabulary for the same type of variable 90 | 91 | **Bad:** 92 | 93 | ```python 94 | getUserInfo() 95 | getUserData() 96 | getUserRecord() 97 | getUserProfile() 98 | ``` 99 | 100 | **Good:** 101 | 102 | ```python 103 | getUser() 104 | ``` 105 | 106 | **[⬆ back to top](#table-of-contents)** 107 | 108 | ### Use searchable names (part 1) 109 | 110 | We will read more code than we will ever write. It's important that the code we do write is 111 | readable and searchable. By *not* naming variables that end up being meaningful for 112 | understanding our program, we hurt our readers. 113 | Make your names searchable. 114 | 115 | **Bad:** 116 | 117 | ```python 118 | # What the heck is 448 for? 119 | result = serializer.serialize(data, 448) 120 | ``` 121 | 122 | **Good:** 123 | 124 | ```python 125 | json = serializer.serialize(data, sort_keys=True, indent=4) 126 | ``` 127 | 128 | ### Use searchable names (part 2) 129 | 130 | **Bad:** 131 | 132 | ```python 133 | # What the heck is 4 for? 134 | class User: 135 | 136 | access = 0 137 | 138 | def __init__(self, access): 139 | self.access = access 140 | 141 | def update(self): 142 | if user.access & 4: 143 | # ... 144 | ``` 145 | 146 | **Good:** 147 | 148 | ```python 149 | class User: 150 | ACCESS_READ = 1 151 | ACCESS_CREATE = 2 152 | ACCESS_UPDATE = 4 153 | ACCESS_DELETE = 8 154 | 155 | access = 0 156 | 157 | def __init__(self, access): 158 | self.access = access 159 | 160 | def update(self): 161 | if user.access & self.ACCESS_UPDATE): 162 | # do edit ... 163 | ``` 164 | 165 | **[⬆ back to top](#table-of-contents)** 166 | 167 | ### Use explanatory variables 168 | 169 | **Bad:** 170 | 171 | ```python 172 | import re 173 | 174 | address = 'One Infinite Loop, Cupertino 95014' 175 | 176 | city_zip_code_regex = re.compile("^[^,]+,\s*(.+?)\s*(\d{5})$") 177 | 178 | matches = city_zip_code_regex.match(address) 179 | 180 | save_city_zip_code(matches.group(1), matches.group(2)) 181 | ``` 182 | 183 | **Not bad:** 184 | 185 | It's better, but we are still heavily dependent on regex. 186 | 187 | ```python 188 | import re 189 | 190 | address = 'One Infinite Loop, Cupertino 95014' 191 | 192 | city_zip_code_regex = re.compile("^[^,]+,\s*(.+?)\s*(\d{5})$") 193 | 194 | matches = city_zip_code_regex.match(address) 195 | 196 | city, zipcode = matches.group(1), matches.group(2) 197 | 198 | save_city_zip_code(city, zipcode) 199 | ``` 200 | 201 | **Good:** 202 | 203 | Decrease dependence on regex by naming subpatterns. 204 | 205 | ```python 206 | address = 'One Infinite Loop, Cupertino 95014' 207 | 208 | city_zip_code_regex = re.compile("^[^,]+,\s*(?P.+?)\s*(?P\d{5})$") 209 | 210 | matches = city_zip_code_regex.match(address) 211 | 212 | save_city_zip_code(matches.group('city'), matches.group('zipcode')) 213 | ``` 214 | 215 | **[⬆ back to top](#table-of-contents)** 216 | 217 | ### Avoid nesting too deeply and return early (part 1) 218 | 219 | Too many if-else statements can make your code hard to follow. Explicit is better 220 | than implicit. 221 | 222 | **Bad:** 223 | 224 | ```python 225 | def is_shop_open(day: str): 226 | if day: 227 | day = day.lower() 228 | if day == 'friday': 229 | return True 230 | elif day == 'saturday': 231 | return True 232 | elif day == 'sunday': 233 | return True 234 | else: 235 | return False 236 | else: 237 | return False 238 | ``` 239 | 240 | **Good:** 241 | 242 | ```python 243 | def is_shop_open(day: str): 244 | 245 | for open_day in ['friday','saturday','sunday']: 246 | if day == open_day: 247 | return True 248 | 249 | return False 250 | ``` 251 | 252 | **[⬆ back to top](#table-of-contents)** 253 | 254 | ### Avoid nesting too deeply and return early (part 2) 255 | 256 | **Bad:** 257 | 258 | ```python 259 | def fibonacci(n): 260 | if n < 50: 261 | if n != 0: 262 | if n != 1: 263 | return fibonacci(n-1) + fibonacci(n-2) 264 | else: 265 | return 1 266 | else: 267 | return 0 268 | 269 | else: 270 | raise OverflowError 271 | ``` 272 | 273 | **Good:** 274 | 275 | ```python 276 | def fibonacci(n): 277 | if n == 0 or n == 1: 278 | return n 279 | 280 | if n > 50: 281 | raise OverflowError 282 | 283 | return fibonacci(n-1) + fibonacci(n-2) 284 | ``` 285 | 286 | **[⬆ back to top](#table-of-contents)** 287 | 288 | ### Avoid Mental Mapping 289 | 290 | Don’t force the reader of your code to translate what the variable means. 291 | Explicit is better than implicit. 292 | 293 | **Bad:** 294 | 295 | ```python 296 | l = ['Austin', 'New York', 'San Francisco'] 297 | 298 | i = 0 299 | while i < len(l): 300 | li = l[i] 301 | doStuff() 302 | doSomeOtherStuff() 303 | # ... 304 | # ... 305 | # ... 306 | # ... 307 | dispatch(li) 308 | i += 1 309 | ``` 310 | 311 | **Good:** 312 | 313 | ```python 314 | locations = ['Austin', 'New York', 'San Francisco'] 315 | 316 | for location in locations: 317 | doStuff() 318 | doSomeOtherStuff() 319 | # ... 320 | # ... 321 | # ... 322 | # ... 323 | dispatch(location) 324 | ``` 325 | 326 | **[⬆ back to top](#table-of-contents)** 327 | 328 | ### Don't add unneeded context 329 | 330 | If your class/object name tells you something, don't repeat that in your 331 | variable name. 332 | 333 | **Bad:** 334 | 335 | ```python 336 | class Car: 337 | 338 | carMake = '' 339 | carModle = '' 340 | carColor = '' 341 | #... 342 | } 343 | ``` 344 | 345 | **Good:** 346 | 347 | ```python 348 | class Car: 349 | make = '' 350 | modle = '' 351 | color = '' 352 | #... 353 | ``` 354 | 355 | **[⬆ back to top](#table-of-contents)** 356 | 357 | ### Use default arguments instead of short circuiting or conditionals 358 | 359 | **Not good:** 360 | 361 | This is not good because `breweryName` can be `NULL`. 362 | 363 | ```python 364 | def createMicrobrewery(breweryName): 365 | 366 | ``` 367 | 368 | **Not bad:** 369 | 370 | This opinion is more understandable than the previous version, but it better controls the value of the variable. 371 | 372 | ```python 373 | def createMicrobrewery(breweryName = ''): 374 | breweryName = breweryName or 'Hipster Brew Co.' 375 | # ... 376 | } 377 | ``` 378 | 379 | **Good:** 380 | 381 | You can use function annotations and be sure that the `breweryName` will not be `NULL`. 382 | 383 | ```python 384 | def createMicrobrewery(breweryName: str = 'Hipster Brew Co.'): 385 |    # ... 386 | ``` 387 | 388 | **[⬆ back to top](#table-of-contents)** 389 | 390 | ## Comparison 391 | 392 | type checking is an antipattern 393 | 394 | **Not good:** 395 | 396 | ```python 397 | a = '42' 398 | b = 42 399 | 400 | if type(a) == type(b): 401 | #... 402 | ``` 403 | 404 | **Good:** 405 | 406 | The identical comparison will compare type and value. 407 | 408 | ```python 409 | a = '42' 410 | b = 42 411 | 412 | if a == b: 413 | # The expression is verified 414 | ``` 415 | 416 | **[⬆ back to top](#table-of-contents)** 417 | 418 | 419 | ## Functions 420 | 421 | ### Function arguments (2 or fewer ideally) 422 | 423 | Limiting the amount of function parameters is incredibly important because it makes 424 | testing your function easier. Having more than three leads to a combinatorial explosion 425 | where you have to test tons of different cases with each separate argument. 426 | 427 | Zero arguments is the ideal case. One or two arguments is ok, and three should be avoided. 428 | Anything more than that should be consolidated. Usually, if you have more than two 429 | arguments then your function is trying to do too much. In cases where it's not, most 430 | of the time a single higher-level object will suffice as an argument. 431 | 432 | **Bad:** 433 | 434 | ```python 435 | def createMenu(title: str, body: str, buttonText: str, cancellable: bool): 436 | # ... 437 | ``` 438 | 439 | **Good:** 440 | 441 | ```python 442 | class MenuConfig: 443 | title = '' 444 | body = '' 445 | buttonText = '' 446 | cancellable = False 447 | 448 | 449 | config = MenuConfig() 450 | config.title = 'Foo' 451 | config.body = 'Bar' 452 | config.buttonText = 'Baz' 453 | config.cancellable = True 454 | 455 | def createMenu(config: MenuConfig): 456 | # ... 457 | ``` 458 | 459 | **[⬆ back to top](#table-of-contents)** 460 | 461 | ### Functions should do one thing 462 | 463 | This is by far the most important rule in software engineering. When functions do more 464 | than one thing, they are harder to compose, test, and reason about. When you can isolate 465 | a function to just one action, they can be refactored easily and your code will read much 466 | cleaner. If you take nothing else away from this guide other than this, you'll be ahead 467 | of many developers. 468 | 469 | **Bad:** 470 | ```python 471 | def emailClients(databaseclient: DBClient, clients: tuple): 472 | for client in clients: 473 | clientrecord = databaseclient.find(client) 474 | if clientrecord.isActive(): 475 | email(client) 476 | ``` 477 | 478 | **Good:** 479 | 480 | ```python 481 | def emailClients(databaseclient: DBClient, clients: tuple): 482 | 483 | activeClientList = [ client for client in clients if isClientActive(databaseclient, client)] 484 | 485 | for activeclient in activeClientList: 486 | email(activeclient) 487 | 488 | def isClientActive((databaseclient: DBClient, client_id: int): 489 | clientrecord = databaseclient->find(client_id) 490 | 491 | return clientrecord->isActive() 492 | ``` 493 | 494 | **[⬆ back to top](#table-of-contents)** 495 | 496 | ### Function names should say what they do 497 | 498 | **Bad:** 499 | 500 | ```python 501 | class Email: 502 | 503 | def handle(self): 504 | mail(self.to, self.subject, self.body) 505 | 506 | message = Email() 507 | 508 | # What is this? A handle for the message? Are we writing to a file now? 509 | message.handle() 510 | ``` 511 | 512 | **Good:** 513 | 514 | ```python 515 | class Email: 516 | 517 | def send(self): 518 | mail(self.to, self.subject, self.body) 519 | 520 | message = Email() 521 | 522 | # Clear and obvious 523 | message->send() 524 | ``` 525 | 526 | **[⬆ back to top](#table-of-contents)** 527 | 528 | ### Functions should only be one level of abstraction 529 | 530 | When you have more than one level of abstraction your function is usually 531 | doing too much. Splitting up functions leads to reusability and easier 532 | testing. 533 | 534 | **Bad:** 535 | 536 | ```python 537 | def parseBetterJSAlternative(code: str): 538 | regexes = [ 539 | # ... 540 | ] 541 | 542 | statements = code.split() 543 | tokens = [] 544 | for regex in regexes: 545 | for statement in statements: 546 | # ... 547 | 548 | ast = [] 549 | 550 | for token in tokens: 551 | # lex ... 552 | 553 | for node in ast: 554 | # parse ... 555 | ``` 556 | 557 | **Bad too:** 558 | 559 | We have carried out some of the functionality, but the `parseBetterJSAlternative()` function is still very complex and not testable. 560 | 561 | ```python 562 | def tokenize(code: str): 563 | regexes = [ 564 | # ... 565 | ] 566 | 567 | statements = code.split() 568 | 569 | tokens = [] 570 | 571 | for regex in regexes: 572 | for statement in statements: 573 | match = regex.match(statement) 574 | 575 | if match: 576 | token.append(match.group(1)) 577 | 578 | return tokens 579 | 580 | def lexer(tokens: list): 581 | ast = [] 582 | 583 | for token in tokens: 584 | # ast.append ... 585 | 586 | return ast 587 | } 588 | 589 | def parseBetterJSAlternative(code: str): 590 | 591 | tokens = tokenize(code) 592 | ast = lexer(tokens) 593 | for node in ast: 594 | # parse .. 595 | ``` 596 | 597 | **Good:** 598 | 599 | The best solution is move out the dependencies of `parseBetterJSAlternative()` function. 600 | 601 | ```python 602 | class Tokenizer: 603 | 604 | def tokenize(code: str): 605 | 606 | regexes = [ 607 | # ... 608 | ] 609 | 610 | statements = code.split() 611 | 612 | tokens = [] 613 | 614 | for regex in regexes: 615 | for statement in statements: 616 | match = regex.match(statement) 617 | 618 | if match: 619 | token.append(match.group(1)) 620 | 621 | return tokens 622 | 623 | class Lexer: 624 | 625 | def lexify(tokens: list): 626 | ast = [] 627 | 628 | for token in tokens: 629 | # ast.append ... 630 | 631 | return ast 632 | 633 | class BetterJSAlternative: 634 | 635 | tokenizer 636 | lexer 637 | 638 | def __init__(self, tokenizer, lexer): 639 | self.tokenizer = tokenizer 640 | self.lexer = lexer 641 | 642 | def parse(self, code: str): 643 | 644 | tokens = self.tokenizer.tokenize(code) 645 | ast = self.lexer.lexify(tokens) 646 | 647 | for node in ast: 648 | # parse ... 649 | ``` 650 | 651 | **[⬆ back to top](#table-of-contents)** 652 | 653 | ### Don't use flags as function parameters 654 | 655 | Flags tell your user that this function does more than one thing. Functions should 656 | do one thing. Split out your functions if they are following different code paths 657 | based on a boolean. 658 | 659 | **Bad:** 660 | 661 | ```python 662 | def createFile(name: str, temp: bool = False): 663 | if temp: 664 | touch('./temp/' + name) 665 | else: 666 | touch(name) 667 | ``` 668 | 669 | **Good:** 670 | 671 | ```python 672 | def createFile(name: str): 673 | touch(name) 674 | 675 | def createTempFile(name: str): 676 | touch('./temp/' + name) 677 | ``` 678 | 679 | **[⬆ back to top](#table-of-contents)** 680 | 681 | ### Avoid Side Effects 682 | 683 | A function produces a side effect if it does anything other than take a value in and 684 | return another value or values. A side effect could be writing to a file, modifying 685 | some global variable, or accidentally wiring all your money to a stranger. 686 | 687 | Now, you do need to have side effects in a program on occasion. Like the previous 688 | example, you might need to write to a file. What you want to do is to centralize where 689 | you are doing this. Don't have several functions and classes that write to a particular 690 | file. Have one service that does it. One and only one. 691 | 692 | The main point is to avoid common pitfalls like sharing state between objects without 693 | any structure, using mutable data types that can be written to by anything, and not 694 | centralizing where your side effects occur. If you can do this, you will be happier 695 | than the vast majority of other programmers. 696 | 697 | **Bad:** 698 | 699 | ```python 700 | // Global variable referenced by following function. 701 | // If we had another function that used this name, now it'd be an array and it could break it. 702 | name = 'Ryan McDermott' 703 | 704 | def splitIntoFirstAndLastName(): 705 | { 706 | global name 707 | name = explode(' ', name) 708 | } 709 | 710 | splitIntoFirstAndLastName() 711 | 712 | print(name) 713 | ``` 714 | 715 | **Good:** 716 | 717 | ```python 718 | def splitIntoFirstAndLastName(name: str): 719 | return name.split() 720 | 721 | name = 'Ryan McDermott' 722 | newName = splitIntoFirstAndLastName(name) 723 | 724 | print(name) 725 | print(newName) 726 | ``` 727 | 728 | **[⬆ back to top](#table-of-contents)** 729 | 730 | ### Don't write to global functions 731 | 732 | Polluting globals is a bad practice in many languages because you could clash with another 733 | library and the user of your API would be none-the-wiser until they get an exception in 734 | production. Let's think about an example: what if you wanted to have configuration array? 735 | You could write global function like `config()`, but it could clash with another library 736 | that tried to do the same thing. 737 | 738 | **Bad:** 739 | 740 | ```python 741 | def config(): 742 | return {'foo':'bar'} 743 | ``` 744 | 745 | **Good:** 746 | 747 | ```python 748 | class Configuration: 749 | 750 | _configuration = {} 751 | 752 | 753 | def __init__(self, configuration: dict): 754 | self._configuration = configuration 755 | 756 | def get(key: str) 757 | return self._configuration[key] if key in self._configuration else '' 758 | ``` 759 | 760 | Load configuration and create instance of `Configuration` class 761 | 762 | ```python 763 | configuration = Configuration({'foo':'bar'}) 764 | ``` 765 | 766 | And now you must use instance of `Configuration` in your application. 767 | 768 | **[⬆ back to top](#table-of-contents)** 769 | 770 | #TODO 771 | ### Don't use a Singleton pattern 772 | Singleton is an [anti-pattern](https://en.wikipedia.org/wiki/Singleton_pattern). Paraphrased from Brian Button: 773 | 1. They are generally used as a **global instance**, why is that so bad? Because **you hide the dependencies** of your application in your code, instead of exposing them through the interfaces. Making something global to avoid passing it around is a [code smell](https://en.wikipedia.org/wiki/Code_smell). 774 | 2. They violate the [single responsibility principle](#single-responsibility-principle-srp): by virtue of the fact that **they control their own creation and lifecycle**. 775 | 3. They inherently cause code to be tightly [coupled](https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29). This makes faking them out under **test rather difficult** in many cases. 776 | 4. They carry state around for the lifetime of the application. Another hit to testing since **you can end up with a situation where tests need to be ordered** which is a big no for unit tests. Why? Because each unit test should be independent from the other. 777 | 778 | There is also very good thoughts by [Misko Hevery](http://misko.hevery.com/about/) about the [root of problem](http://misko.hevery.com/2008/08/25/root-cause-of-singletons/). 779 | 780 | **Bad:** 781 | 782 | ```python 783 | class DBConnection 784 | { 785 | private static $instance; 786 | 787 | private function __construct(string $dsn) 788 | { 789 | // ... 790 | } 791 | 792 | public static function getInstance(): DBConnection 793 | { 794 | if (self::$instance === null) { 795 | self::$instance = new self(); 796 | } 797 | 798 | return self::$instance; 799 | } 800 | 801 | // ... 802 | } 803 | 804 | $singleton = DBConnection::getInstance(); 805 | ``` 806 | 807 | **Good:** 808 | 809 | ```python 810 | class DBConnection 811 | { 812 | public function __construct(string $dsn) 813 | { 814 | // ... 815 | } 816 | 817 | // ... 818 | } 819 | ``` 820 | 821 | Create instance of `DBConnection` class and configure it with [DSN](http://python.net/manual/en/pdo.construct.python#refsect1-pdo.construct-parameters). 822 | 823 | ```python 824 | $connection = new DBConnection($dsn); 825 | ``` 826 | 827 | And now you must use instance of `DBConnection` in your application. 828 | 829 | **[⬆ back to top](#table-of-contents)** 830 | 831 | ### Encapsulate conditionals 832 | 833 | **Bad:** 834 | 835 | ```python 836 | if ($article->state === 'published') { 837 | // ... 838 | } 839 | ``` 840 | 841 | **Good:** 842 | 843 | ```python 844 | if ($article->isPublished()) { 845 | // ... 846 | } 847 | ``` 848 | 849 | **[⬆ back to top](#table-of-contents)** 850 | 851 | ### Avoid negative conditionals 852 | 853 | **Bad:** 854 | 855 | ```python 856 | function isDOMNodeNotPresent(\DOMNode $node): bool 857 | { 858 | // ... 859 | } 860 | 861 | if (!isDOMNodeNotPresent($node)) 862 | { 863 | // ... 864 | } 865 | ``` 866 | 867 | **Good:** 868 | 869 | ```python 870 | function isDOMNodePresent(\DOMNode $node): bool 871 | { 872 | // ... 873 | } 874 | 875 | if (isDOMNodePresent($node)) { 876 | // ... 877 | } 878 | ``` 879 | 880 | **[⬆ back to top](#table-of-contents)** 881 | 882 | ### Avoid conditionals 883 | 884 | This seems like an impossible task. Upon first hearing this, most people say, 885 | "how am I supposed to do anything without an `if` statement?" The answer is that 886 | you can use polymorphism to achieve the same task in many cases. The second 887 | question is usually, "well that's great but why would I want to do that?" The 888 | answer is a previous clean code concept we learned: a function should only do 889 | one thing. When you have classes and functions that have `if` statements, you 890 | are telling your user that your function does more than one thing. Remember, 891 | just do one thing. 892 | 893 | **Bad:** 894 | 895 | ```python 896 | class Airplane 897 | { 898 | // ... 899 | 900 | public function getCruisingAltitude(): int 901 | { 902 | switch ($this->type) { 903 | case '777': 904 | return $this->getMaxAltitude() - $this->getPassengerCount(); 905 | case 'Air Force One': 906 | return $this->getMaxAltitude(); 907 | case 'Cessna': 908 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 909 | } 910 | } 911 | } 912 | ``` 913 | 914 | **Good:** 915 | 916 | ```python 917 | interface Airplane 918 | { 919 | // ... 920 | 921 | public function getCruisingAltitude(): int; 922 | } 923 | 924 | class Boeing777 implements Airplane 925 | { 926 | // ... 927 | 928 | public function getCruisingAltitude(): int 929 | { 930 | return $this->getMaxAltitude() - $this->getPassengerCount(); 931 | } 932 | } 933 | 934 | class AirForceOne implements Airplane 935 | { 936 | // ... 937 | 938 | public function getCruisingAltitude(): int 939 | { 940 | return $this->getMaxAltitude(); 941 | } 942 | } 943 | 944 | class Cessna implements Airplane 945 | { 946 | // ... 947 | 948 | public function getCruisingAltitude(): int 949 | { 950 | return $this->getMaxAltitude() - $this->getFuelExpenditure(); 951 | } 952 | } 953 | ``` 954 | 955 | **[⬆ back to top](#table-of-contents)** 956 | 957 | ### Avoid type-checking (part 1) 958 | 959 | Python is untyped, which means your functions can take any type of argument. 960 | Sometimes you are bitten by this freedom and it becomes tempting to do 961 | type-checking in your functions. There are many ways to avoid having to do this. 962 | The first thing to consider is consistent APIs. 963 | 964 | **Bad:** 965 | 966 | ```python 967 | function travelToTexas($vehicle): void 968 | { 969 | if ($vehicle instanceof Bicycle) { 970 | $vehicle->pedalTo(new Location('texas')); 971 | } elseif ($vehicle instanceof Car) { 972 | $vehicle->driveTo(new Location('texas')); 973 | } 974 | } 975 | ``` 976 | 977 | **Good:** 978 | 979 | ```python 980 | function travelToTexas(Traveler $vehicle): void 981 | { 982 | $vehicle->travelTo(new Location('texas')); 983 | } 984 | ``` 985 | 986 | **[⬆ back to top](#table-of-contents)** 987 | 988 | ### Avoid type-checking (part 2) 989 | 990 | If you are working with basic primitive values like strings, integers, and arrays, 991 | and you use Python 7+ and you can't use polymorphism but you still feel the need to 992 | type-check, you should consider 993 | [type declaration](http://python.net/manual/en/functions.arguments.python#functions.arguments.type-declaration) 994 | or strict mode. It provides you with static typing on top of standard Python syntax. 995 | The problem with manually type-checking is that doing it will require so much 996 | extra verbiage that the faux "type-safety" you get doesn't make up for the lost 997 | readability. Keep your Python clean, write good tests, and have good code reviews. 998 | Otherwise, do all of that but with Python strict type declaration or strict mode. 999 | 1000 | **Bad:** 1001 | 1002 | ```python 1003 | function combine($val1, $val2): int 1004 | { 1005 | if (!is_numeric($val1) || !is_numeric($val2)) { 1006 | throw new \Exception('Must be of type Number'); 1007 | } 1008 | 1009 | return $val1 + $val2; 1010 | } 1011 | ``` 1012 | 1013 | **Good:** 1014 | 1015 | ```python 1016 | function combine(int $val1, int $val2): int 1017 | { 1018 | return $val1 + $val2; 1019 | } 1020 | ``` 1021 | 1022 | **[⬆ back to top](#table-of-contents)** 1023 | 1024 | ### Remove dead code 1025 | 1026 | Dead code is just as bad as duplicate code. There's no reason to keep it in 1027 | your codebase. If it's not being called, get rid of it! It will still be safe 1028 | in your version history if you still need it. 1029 | 1030 | **Bad:** 1031 | 1032 | ```python 1033 | function oldRequestModule(string $url): void 1034 | { 1035 | // ... 1036 | } 1037 | 1038 | function newRequestModule(string $url): void 1039 | { 1040 | // ... 1041 | } 1042 | 1043 | $request = newRequestModule($requestUrl); 1044 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1045 | ``` 1046 | 1047 | **Good:** 1048 | 1049 | ```python 1050 | function requestModule(string $url): void 1051 | { 1052 | // ... 1053 | } 1054 | 1055 | $request = requestModule($requestUrl); 1056 | inventoryTracker('apples', $request, 'www.inventory-awesome.io'); 1057 | ``` 1058 | 1059 | **[⬆ back to top](#table-of-contents)** 1060 | 1061 | 1062 | ## Objects and Data Structures 1063 | 1064 | ### Use object encapsulation 1065 | 1066 | In Python you can set `public`, `protected` and `private` keywords for methods. 1067 | Using it, you can control properties modification on an object. 1068 | 1069 | * When you want to do more beyond getting an object property, you don't have 1070 | to look up and change every accessor in your codebase. 1071 | * Makes adding validation simple when doing a `set`. 1072 | * Encapsulates the internal representation. 1073 | * Easy to add logging and error handling when getting and setting. 1074 | * Inheriting this class, you can override default functionality. 1075 | * You can lazy load your object's properties, let's say getting it from a 1076 | server. 1077 | 1078 | Additionally, this is part of [Open/Closed](#openclosed-principle-ocp) principle. 1079 | 1080 | **Bad:** 1081 | 1082 | ```python 1083 | class BankAccount 1084 | { 1085 | public $balance = 1000; 1086 | } 1087 | 1088 | $bankAccount = new BankAccount(); 1089 | 1090 | // Buy shoes... 1091 | $bankAccount->balance -= 100; 1092 | ``` 1093 | 1094 | **Good:** 1095 | 1096 | ```python 1097 | class BankAccount 1098 | { 1099 | private $balance; 1100 | 1101 | public function __construct(int $balance = 1000) 1102 | { 1103 | $this->balance = $balance; 1104 | } 1105 | 1106 | public function withdraw(int $amount): void 1107 | { 1108 | if ($amount > $this->balance) { 1109 | throw new \Exception('Amount greater than available balance.'); 1110 | } 1111 | 1112 | $this->balance -= $amount; 1113 | } 1114 | 1115 | public function deposit(int $amount): void 1116 | { 1117 | $this->balance += $amount; 1118 | } 1119 | 1120 |    public function getBalance(): int 1121 | { 1122 | return $this->balance; 1123 | } 1124 | } 1125 | 1126 | $bankAccount = new BankAccount(); 1127 | 1128 | // Buy shoes... 1129 | $bankAccount->withdraw($shoesPrice); 1130 | 1131 | // Get balance 1132 | $balance = $bankAccount->getBalance(); 1133 | ``` 1134 | 1135 | **[⬆ back to top](#table-of-contents)** 1136 | 1137 | ### Make objects have private/protected members 1138 | 1139 | * `public` methods and properties are most dangerous for changes, because some outside code may easily rely on them and you can't control what code relies on them. **Modifications in class are dangerous for all users of class.** 1140 | * `protected` modifier are as dangerous as public, because they are available in scope of any child class. This effectively means that difference between public and protected is only in access mechanism, but encapsulation guarantee remains the same. **Modifications in class are dangerous for all descendant classes.** 1141 | * `private` modifier guarantees that code is **dangerous to modify only in boundaries of single class** (you are safe for modifications and you won't have [Jenga effect](http://www.urbandictionary.com/define.python?term=Jengaphobia&defid=2494196)). 1142 | 1143 | Therefore, use `private` by default and `public/protected` when you need to provide access for external classes. 1144 | 1145 | For more informations you can read the [blog post](http://fabien.potencier.org/pragmatism-over-theory-protected-vs-private.html) on this topic written by [Fabien Potencier](https://github.com/fabpot). 1146 | 1147 | **Bad:** 1148 | 1149 | ```python 1150 | class Employee 1151 | { 1152 | public $name; 1153 | 1154 | public function __construct(string $name) 1155 | { 1156 | $this->name = $name; 1157 | } 1158 | } 1159 | 1160 | $employee = new Employee('John Doe'); 1161 | echo 'Employee name: '.$employee->name; // Employee name: John Doe 1162 | ``` 1163 | 1164 | **Good:** 1165 | 1166 | ```python 1167 | class Employee 1168 | { 1169 | private $name; 1170 | 1171 | public function __construct(string $name) 1172 | { 1173 | $this->name = $name; 1174 | } 1175 | 1176 | public function getName(): string 1177 | { 1178 | return $this->name; 1179 | } 1180 | } 1181 | 1182 | $employee = new Employee('John Doe'); 1183 | echo 'Employee name: '.$employee->getName(); // Employee name: John Doe 1184 | ``` 1185 | 1186 | **[⬆ back to top](#table-of-contents)** 1187 | 1188 | ## Classes 1189 | 1190 | ### Prefer composition over inheritance 1191 | 1192 | As stated famously in [*Design Patterns*](https://en.wikipedia.org/wiki/Design_Patterns) by the Gang of Four, 1193 | you should prefer composition over inheritance where you can. There are lots of 1194 | good reasons to use inheritance and lots of good reasons to use composition. 1195 | The main point for this maxim is that if your mind instinctively goes for 1196 | inheritance, try to think if composition could model your problem better. In some 1197 | cases it can. 1198 | 1199 | You might be wondering then, "when should I use inheritance?" It 1200 | depends on your problem at hand, but this is a decent list of when inheritance 1201 | makes more sense than composition: 1202 | 1203 | 1. Your inheritance represents an "is-a" relationship and not a "has-a" 1204 | relationship (Human->Animal vs. User->UserDetails). 1205 | 2. You can reuse code from the base classes (Humans can move like all animals). 1206 | 3. You want to make global changes to derived classes by changing a base class. 1207 | (Change the caloric expenditure of all animals when they move). 1208 | 1209 | **Bad:** 1210 | 1211 | ```python 1212 | class Employee 1213 | { 1214 | private $name; 1215 | private $email; 1216 | 1217 | public function __construct(string $name, string $email) 1218 | { 1219 | $this->name = $name; 1220 | $this->email = $email; 1221 | } 1222 | 1223 | // ... 1224 | } 1225 | 1226 | // Bad because Employees "have" tax data. 1227 | // EmployeeTaxData is not a type of Employee 1228 | 1229 | class EmployeeTaxData extends Employee 1230 | { 1231 | private $ssn; 1232 | private $salary; 1233 | 1234 | public function __construct(string $name, string $email, string $ssn, string $salary) 1235 | { 1236 | parent::__construct($name, $email); 1237 | 1238 | $this->ssn = $ssn; 1239 | $this->salary = $salary; 1240 | } 1241 | 1242 | // ... 1243 | } 1244 | ``` 1245 | 1246 | **Good:** 1247 | 1248 | ```python 1249 | class EmployeeTaxData 1250 | { 1251 | private $ssn; 1252 | private $salary; 1253 | 1254 | public function __construct(string $ssn, string $salary) 1255 | { 1256 | $this->ssn = $ssn; 1257 | $this->salary = $salary; 1258 | } 1259 | 1260 | // ... 1261 | } 1262 | 1263 | class Employee 1264 | { 1265 | private $name; 1266 | private $email; 1267 | private $taxData; 1268 | 1269 | public function __construct(string $name, string $email) 1270 | { 1271 | $this->name = $name; 1272 | $this->email = $email; 1273 | } 1274 | 1275 | public function setTaxData(string $ssn, string $salary) 1276 | { 1277 | $this->taxData = new EmployeeTaxData($ssn, $salary); 1278 | } 1279 | 1280 | // ... 1281 | } 1282 | ``` 1283 | 1284 | **[⬆ back to top](#table-of-contents)** 1285 | 1286 | ### Avoid fluent interfaces 1287 | 1288 | A [Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) is an object 1289 | oriented API that aims to improve the readability of the source code by using 1290 | [Method chaining](https://en.wikipedia.org/wiki/Method_chaining). 1291 | 1292 | While there can be some contexts, frequently builder objects, where this 1293 | pattern reduces the verbosity of the code (for example the [PythonUnit Mock Builder](https://pythonunit.de/manual/current/en/test-doubles.html) 1294 | or the [Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html)), 1295 | more often it comes at some costs: 1296 | 1297 | 1. Breaks [Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29). 1298 | 2. Breaks [Decorators](https://en.wikipedia.org/wiki/Decorator_pattern). 1299 | 3. Is harder to [mock](https://en.wikipedia.org/wiki/Mock_object) in a test suite. 1300 | 4. Makes diffs of commits harder to read. 1301 | 1302 | For more informations you can read the full [blog post](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) 1303 | on this topic written by [Marco Pivetta](https://github.com/Ocramius). 1304 | 1305 | **Bad:** 1306 | 1307 | ```python 1308 | class Car 1309 | { 1310 | private $make = 'Honda'; 1311 | private $model = 'Accord'; 1312 | private $color = 'white'; 1313 | 1314 | public function setMake(string $make): self 1315 | { 1316 | $this->make = $make; 1317 | 1318 | // NOTE: Returning this for chaining 1319 | return $this; 1320 | } 1321 | 1322 | public function setModel(string $model): self 1323 | { 1324 | $this->model = $model; 1325 | 1326 | // NOTE: Returning this for chaining 1327 | return $this; 1328 | } 1329 | 1330 | public function setColor(string $color): self 1331 | { 1332 | $this->color = $color; 1333 | 1334 | // NOTE: Returning this for chaining 1335 | return $this; 1336 | } 1337 | 1338 | public function dump(): void 1339 | { 1340 | var_dump($this->make, $this->model, $this->color); 1341 | } 1342 | } 1343 | 1344 | $car = (new Car()) 1345 | ->setColor('pink') 1346 | ->setMake('Ford') 1347 | ->setModel('F-150') 1348 | ->dump(); 1349 | ``` 1350 | 1351 | **Good:** 1352 | 1353 | ```python 1354 | class Car 1355 | { 1356 | private $make = 'Honda'; 1357 | private $model = 'Accord'; 1358 | private $color = 'white'; 1359 | 1360 | public function setMake(string $make): void 1361 | { 1362 | $this->make = $make; 1363 | } 1364 | 1365 | public function setModel(string $model): void 1366 | { 1367 | $this->model = $model; 1368 | } 1369 | 1370 | public function setColor(string $color): void 1371 | { 1372 | $this->color = $color; 1373 | } 1374 | 1375 | public function dump(): void 1376 | { 1377 | var_dump($this->make, $this->model, $this->color); 1378 | } 1379 | } 1380 | 1381 | $car = new Car(); 1382 | $car->setColor('pink'); 1383 | $car->setMake('Ford'); 1384 | $car->setModel('F-150'); 1385 | $car->dump(); 1386 | ``` 1387 | 1388 | **[⬆ back to top](#table-of-contents)** 1389 | 1390 | ### Prefer final classes 1391 | 1392 | The `final` should be used whenever possible: 1393 | 1394 | 1. It prevents uncontrolled inheritance chain. 1395 | 2. It encourages [composition](#prefer-composition-over-inheritance). 1396 | 3. It encourages the [Single Responsibility Pattern](#single-responsibility-principle-srp). 1397 | 4. It encourages developers to use your public methods instead of extending the class to get access on protected ones. 1398 | 5. It allows you to change your code without any break of applications that use your class. 1399 | 1400 | The only condition is that your class should implement an interface and no other public methods are defined. 1401 | 1402 | For more informations you can read [the blog post](https://ocramius.github.io/blog/when-to-declare-classes-final/) on this topic written by [Marco Pivetta (Ocramius)](https://ocramius.github.io/). 1403 | 1404 | **Bad:** 1405 | 1406 | ```python 1407 | final class Car 1408 | { 1409 | private $color; 1410 | 1411 | public function __construct($color) 1412 | { 1413 | $this->color = $color; 1414 | } 1415 | 1416 | /** 1417 | * @return string The color of the vehicle 1418 | */ 1419 | public function getColor() 1420 | { 1421 | return $this->color; 1422 | } 1423 | } 1424 | ``` 1425 | 1426 | **Good:** 1427 | 1428 | ```python 1429 | interface Vehicle 1430 | { 1431 | /** 1432 | * @return string The color of the vehicle 1433 | */ 1434 | public function getColor(); 1435 | } 1436 | 1437 | final class Car implements Vehicle 1438 | { 1439 | private $color; 1440 | 1441 | public function __construct($color) 1442 | { 1443 | $this->color = $color; 1444 | } 1445 | 1446 | /** 1447 | * {@inheritdoc} 1448 | */ 1449 | public function getColor() 1450 | { 1451 | return $this->color; 1452 | } 1453 | } 1454 | ``` 1455 | 1456 | **[⬆ back to top](#table-of-contents)** 1457 | 1458 | ## SOLID 1459 | 1460 | **SOLID** is the mnemonic acronym introduced by Michael Feathers for the first five principles named by Robert Martin, which meant five basic principles of object-oriented programming and design. 1461 | 1462 | * [S: Single Responsibility Principle (SRP)](#single-responsibility-principle-srp) 1463 | * [O: Open/Closed Principle (OCP)](#openclosed-principle-ocp) 1464 | * [L: Liskov Substitution Principle (LSP)](#liskov-substitution-principle-lsp) 1465 | * [I: Interface Segregation Principle (ISP)](#interface-segregation-principle-isp) 1466 | * [D: Dependency Inversion Principle (DIP)](#dependency-inversion-principle-dip) 1467 | 1468 | ### Single Responsibility Principle (SRP) 1469 | 1470 | As stated in Clean Code, "There should never be more than one reason for a class 1471 | to change". It's tempting to jam-pack a class with a lot of functionality, like 1472 | when you can only take one suitcase on your flight. The issue with this is 1473 | that your class won't be conceptually cohesive and it will give it many reasons 1474 | to change. Minimizing the amount of times you need to change a class is important. 1475 | It's important because if too much functionality is in one class and you modify a piece of it, 1476 | it can be difficult to understand how that will affect other dependent modules in 1477 | your codebase. 1478 | 1479 | **Bad:** 1480 | 1481 | ```python 1482 | class UserSettings 1483 | { 1484 | private $user; 1485 | 1486 | public function __construct(User $user) 1487 | { 1488 | $this->user = $user; 1489 | } 1490 | 1491 | public function changeSettings(array $settings): void 1492 | { 1493 | if ($this->verifyCredentials()) { 1494 | // ... 1495 | } 1496 | } 1497 | 1498 | private function verifyCredentials(): bool 1499 | { 1500 | // ... 1501 | } 1502 | } 1503 | ``` 1504 | 1505 | **Good:** 1506 | 1507 | ```python 1508 | class UserAuth 1509 | { 1510 | private $user; 1511 | 1512 | public function __construct(User $user) 1513 | { 1514 | $this->user = $user; 1515 | } 1516 | 1517 | public function verifyCredentials(): bool 1518 | { 1519 | // ... 1520 | } 1521 | } 1522 | 1523 | class UserSettings 1524 | { 1525 | private $user; 1526 | private $auth; 1527 | 1528 | public function __construct(User $user) 1529 | { 1530 | $this->user = $user; 1531 | $this->auth = new UserAuth($user); 1532 | } 1533 | 1534 | public function changeSettings(array $settings): void 1535 | { 1536 | if ($this->auth->verifyCredentials()) { 1537 | // ... 1538 | } 1539 | } 1540 | } 1541 | ``` 1542 | 1543 | **[⬆ back to top](#table-of-contents)** 1544 | 1545 | ### Open/Closed Principle (OCP) 1546 | 1547 | As stated by Bertrand Meyer, "software entities (classes, modules, functions, 1548 | etc.) should be open for extension, but closed for modification." What does that 1549 | mean though? This principle basically states that you should allow users to 1550 | add new functionalities without changing existing code. 1551 | 1552 | **Bad:** 1553 | 1554 | ```python 1555 | abstract class Adapter 1556 | { 1557 | protected $name; 1558 | 1559 | public function getName(): string 1560 | { 1561 | return $this->name; 1562 | } 1563 | } 1564 | 1565 | class AjaxAdapter extends Adapter 1566 | { 1567 | public function __construct() 1568 | { 1569 | parent::__construct(); 1570 | 1571 | $this->name = 'ajaxAdapter'; 1572 | } 1573 | } 1574 | 1575 | class NodeAdapter extends Adapter 1576 | { 1577 | public function __construct() 1578 | { 1579 | parent::__construct(); 1580 | 1581 | $this->name = 'nodeAdapter'; 1582 | } 1583 | } 1584 | 1585 | class HttpRequester 1586 | { 1587 | private $adapter; 1588 | 1589 | public function __construct(Adapter $adapter) 1590 | { 1591 | $this->adapter = $adapter; 1592 | } 1593 | 1594 | public function fetch(string $url): Promise 1595 | { 1596 | $adapterName = $this->adapter->getName(); 1597 | 1598 | if ($adapterName === 'ajaxAdapter') { 1599 | return $this->makeAjaxCall($url); 1600 | } elseif ($adapterName === 'httpNodeAdapter') { 1601 | return $this->makeHttpCall($url); 1602 | } 1603 | } 1604 | 1605 | private function makeAjaxCall(string $url): Promise 1606 | { 1607 | // request and return promise 1608 | } 1609 | 1610 | private function makeHttpCall(string $url): Promise 1611 | { 1612 | // request and return promise 1613 | } 1614 | } 1615 | ``` 1616 | 1617 | **Good:** 1618 | 1619 | ```python 1620 | interface Adapter 1621 | { 1622 | public function request(string $url): Promise; 1623 | } 1624 | 1625 | class AjaxAdapter implements Adapter 1626 | { 1627 | public function request(string $url): Promise 1628 | { 1629 | // request and return promise 1630 | } 1631 | } 1632 | 1633 | class NodeAdapter implements Adapter 1634 | { 1635 | public function request(string $url): Promise 1636 | { 1637 | // request and return promise 1638 | } 1639 | } 1640 | 1641 | class HttpRequester 1642 | { 1643 | private $adapter; 1644 | 1645 | public function __construct(Adapter $adapter) 1646 | { 1647 | $this->adapter = $adapter; 1648 | } 1649 | 1650 | public function fetch(string $url): Promise 1651 | { 1652 | return $this->adapter->request($url); 1653 | } 1654 | } 1655 | ``` 1656 | 1657 | **[⬆ back to top](#table-of-contents)** 1658 | 1659 | ### Liskov Substitution Principle (LSP) 1660 | 1661 | This is a scary term for a very simple concept. It's formally defined as "If S 1662 | is a subtype of T, then objects of type T may be replaced with objects of type S 1663 | (i.e., objects of type S may substitute objects of type T) without altering any 1664 | of the desirable properties of that program (correctness, task performed, 1665 | etc.)." That's an even scarier definition. 1666 | 1667 | The best explanation for this is if you have a parent class and a child class, 1668 | then the base class and child class can be used interchangeably without getting 1669 | incorrect results. This might still be confusing, so let's take a look at the 1670 | classic Square-Rectangle example. Mathematically, a square is a rectangle, but 1671 | if you model it using the "is-a" relationship via inheritance, you quickly 1672 | get into trouble. 1673 | 1674 | **Bad:** 1675 | 1676 | ```python 1677 | class Rectangle 1678 | { 1679 | protected $width = 0; 1680 | protected $height = 0; 1681 | 1682 | public function setWidth(int $width): void 1683 | { 1684 | $this->width = $width; 1685 | } 1686 | 1687 | public function setHeight(int $height): void 1688 | { 1689 | $this->height = $height; 1690 | } 1691 | 1692 | public function getArea(): int 1693 | { 1694 | return $this->width * $this->height; 1695 | } 1696 | } 1697 | 1698 | class Square extends Rectangle 1699 | { 1700 | public function setWidth(int $width): void 1701 | { 1702 | $this->width = $this->height = $width; 1703 | } 1704 | 1705 | public function setHeight(int $height): void 1706 | { 1707 | $this->width = $this->height = $height; 1708 | } 1709 | } 1710 | 1711 | function printArea(Rectangle $rectangle): void 1712 | { 1713 | $rectangle->setWidth(4); 1714 | $rectangle->setHeight(5); 1715 | 1716 | // BAD: Will return 25 for Square. Should be 20. 1717 | echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).Python_EOL; 1718 | } 1719 | 1720 | $rectangles = [new Rectangle(), new Square()]; 1721 | 1722 | foreach ($rectangles as $rectangle) { 1723 | printArea($rectangle); 1724 | } 1725 | ``` 1726 | 1727 | **Good:** 1728 | 1729 | The best way is separate the quadrangles and allocation of a more general subtype for both shapes. 1730 | 1731 | Despite the apparent similarity of the square and the rectangle, they are different. 1732 | A square has much in common with a rhombus, and a rectangle with a parallelogram, but they are not subtype. 1733 | A square, a rectangle, a rhombus and a parallelogram are separate shapes with their own properties, albeit similar. 1734 | 1735 | ```python 1736 | interface Shape 1737 | { 1738 | public function getArea(): int; 1739 | } 1740 | 1741 | class Rectangle implements Shape 1742 | { 1743 | private $width = 0; 1744 | private $height = 0; 1745 | 1746 | public function __construct(int $width, int $height) 1747 | { 1748 | $this->width = $width; 1749 | $this->height = $height; 1750 | } 1751 | 1752 | public function getArea(): int 1753 | { 1754 | return $this->width * $this->height; 1755 | } 1756 | } 1757 | 1758 | class Square implements Shape 1759 | { 1760 | private $length = 0; 1761 | 1762 | public function __construct(int $length) 1763 | { 1764 | $this->length = $length; 1765 | } 1766 | 1767 | public function getArea(): int 1768 | { 1769 |        return $this->length ** 2; 1770 |    } 1771 | } 1772 | 1773 | function printArea(Shape $shape): void 1774 | { 1775 | echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).Python_EOL; 1776 | } 1777 | 1778 | $shapes = [new Rectangle(4, 5), new Square(5)]; 1779 | 1780 | foreach ($shapes as $shape) { 1781 | printArea($shape); 1782 | } 1783 | ``` 1784 | 1785 | **[⬆ back to top](#table-of-contents)** 1786 | 1787 | ### Interface Segregation Principle (ISP) 1788 | 1789 | ISP states that "Clients should not be forced to depend upon interfaces that 1790 | they do not use." 1791 | 1792 | A good example to look at that demonstrates this principle is for 1793 | classes that require large settings objects. Not requiring clients to set up 1794 | huge amounts of options is beneficial, because most of the time they won't need 1795 | all of the settings. Making them optional helps prevent having a "fat interface". 1796 | 1797 | **Bad:** 1798 | 1799 | ```python 1800 | interface Employee 1801 | { 1802 | public function work(): void; 1803 | 1804 | public function eat(): void; 1805 | } 1806 | 1807 | class Human implements Employee 1808 | { 1809 | public function work(): void 1810 | { 1811 | // ....working 1812 | } 1813 | 1814 | public function eat(): void 1815 | { 1816 | // ...... eating in lunch break 1817 | } 1818 | } 1819 | 1820 | class Robot implements Employee 1821 | { 1822 | public function work(): void 1823 | { 1824 | //.... working much more 1825 | } 1826 | 1827 | public function eat(): void 1828 | { 1829 | //.... robot can't eat, but it must implement this method 1830 | } 1831 | } 1832 | ``` 1833 | 1834 | **Good:** 1835 | 1836 | Not every worker is an employee, but every employee is a worker. 1837 | 1838 | ```python 1839 | interface Workable 1840 | { 1841 | public function work(): void; 1842 | } 1843 | 1844 | interface Feedable 1845 | { 1846 | public function eat(): void; 1847 | } 1848 | 1849 | interface Employee extends Feedable, Workable 1850 | { 1851 | } 1852 | 1853 | class Human implements Employee 1854 | { 1855 | public function work(): void 1856 | { 1857 | // ....working 1858 | } 1859 | 1860 | public function eat(): void 1861 | { 1862 | //.... eating in lunch break 1863 | } 1864 | } 1865 | 1866 | // robot can only work 1867 | class Robot implements Workable 1868 | { 1869 | public function work(): void 1870 | { 1871 | // ....working 1872 | } 1873 | } 1874 | ``` 1875 | 1876 | **[⬆ back to top](#table-of-contents)** 1877 | 1878 | ### Dependency Inversion Principle (DIP) 1879 | 1880 | This principle states two essential things: 1881 | 1. High-level modules should not depend on low-level modules. Both should 1882 | depend on abstractions. 1883 | 2. Abstractions should not depend upon details. Details should depend on 1884 | abstractions. 1885 | 1886 | This can be hard to understand at first, but if you've worked with Python frameworks (like Symfony), you've seen an implementation of this principle in the form of Dependency 1887 | Injection (DI). While they are not identical concepts, DIP keeps high-level 1888 | modules from knowing the details of its low-level modules and setting them up. 1889 | It can accomplish this through DI. A huge benefit of this is that it reduces 1890 | the coupling between modules. Coupling is a very bad development pattern because 1891 | it makes your code hard to refactor. 1892 | 1893 | **Bad:** 1894 | 1895 | ```python 1896 | class Employee 1897 | { 1898 | public function work(): void 1899 | { 1900 | // ....working 1901 | } 1902 | } 1903 | 1904 | class Robot extends Employee 1905 | { 1906 | public function work(): void 1907 | { 1908 | //.... working much more 1909 | } 1910 | } 1911 | 1912 | class Manager 1913 | { 1914 | private $employee; 1915 | 1916 | public function __construct(Employee $employee) 1917 | { 1918 | $this->employee = $employee; 1919 | } 1920 | 1921 | public function manage(): void 1922 | { 1923 | $this->employee->work(); 1924 | } 1925 | } 1926 | ``` 1927 | 1928 | **Good:** 1929 | 1930 | ```python 1931 | interface Employee 1932 | { 1933 | public function work(): void; 1934 | } 1935 | 1936 | class Human implements Employee 1937 | { 1938 | public function work(): void 1939 | { 1940 | // ....working 1941 | } 1942 | } 1943 | 1944 | class Robot implements Employee 1945 | { 1946 | public function work(): void 1947 | { 1948 | //.... working much more 1949 | } 1950 | } 1951 | 1952 | class Manager 1953 | { 1954 | private $employee; 1955 | 1956 | public function __construct(Employee $employee) 1957 | { 1958 | $this->employee = $employee; 1959 | } 1960 | 1961 | public function manage(): void 1962 | { 1963 | $this->employee->work(); 1964 | } 1965 | } 1966 | ``` 1967 | 1968 | **[⬆ back to top](#table-of-contents)** 1969 | 1970 | ## Don’t repeat yourself (DRY) 1971 | 1972 | Try to observe the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principle. 1973 | 1974 | Do your absolute best to avoid duplicate code. Duplicate code is bad because 1975 | it means that there's more than one place to alter something if you need to 1976 | change some logic. 1977 | 1978 | Imagine if you run a restaurant and you keep track of your inventory: all your 1979 | tomatoes, onions, garlic, spices, etc. If you have multiple lists that 1980 | you keep this on, then all have to be updated when you serve a dish with 1981 | tomatoes in them. If you only have one list, there's only one place to update! 1982 | 1983 | Often you have duplicate code because you have two or more slightly 1984 | different things, that share a lot in common, but their differences force you 1985 | to have two or more separate functions that do much of the same things. Removing 1986 | duplicate code means creating an abstraction that can handle this set of different 1987 | things with just one function/module/class. 1988 | 1989 | Getting the abstraction right is critical, that's why you should follow the 1990 | SOLID principles laid out in the [Classes](#classes) section. Bad abstractions can be 1991 | worse than duplicate code, so be careful! Having said this, if you can make 1992 | a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself 1993 | updating multiple places any time you want to change one thing. 1994 | 1995 | **Bad:** 1996 | 1997 | ```python 1998 | function showDeveloperList(array $developers): void 1999 | { 2000 | foreach ($developers as $developer) { 2001 | $expectedSalary = $developer->calculateExpectedSalary(); 2002 | $experience = $developer->getExperience(); 2003 | $githubLink = $developer->getGithubLink(); 2004 | $data = [ 2005 | $expectedSalary, 2006 | $experience, 2007 | $githubLink 2008 | ]; 2009 | 2010 | render($data); 2011 | } 2012 | } 2013 | 2014 | function showManagerList(array $managers): void 2015 | { 2016 | foreach ($managers as $manager) { 2017 | $expectedSalary = $manager->calculateExpectedSalary(); 2018 | $experience = $manager->getExperience(); 2019 | $githubLink = $manager->getGithubLink(); 2020 | $data = [ 2021 | $expectedSalary, 2022 | $experience, 2023 | $githubLink 2024 | ]; 2025 | 2026 | render($data); 2027 | } 2028 | } 2029 | ``` 2030 | 2031 | **Good:** 2032 | 2033 | ```python 2034 | function showList(array $employees): void 2035 | { 2036 | foreach ($employees as $employee) { 2037 | $expectedSalary = $employee->calculateExpectedSalary(); 2038 | $experience = $employee->getExperience(); 2039 | $githubLink = $employee->getGithubLink(); 2040 | $data = [ 2041 | $expectedSalary, 2042 | $experience, 2043 | $githubLink 2044 | ]; 2045 | 2046 | render($data); 2047 | } 2048 | } 2049 | ``` 2050 | 2051 | **Very good:** 2052 | 2053 | It is better to use a compact version of the code. 2054 | 2055 | ```python 2056 | function showList(array $employees): void 2057 | { 2058 | foreach ($employees as $employee) { 2059 | render([ 2060 | $employee->calculateExpectedSalary(), 2061 | $employee->getExperience(), 2062 | $employee->getGithubLink() 2063 | ]); 2064 | } 2065 | } 2066 | ``` 2067 | 2068 | **[⬆ back to top](#table-of-contents)** 2069 | --------------------------------------------------------------------------------