Stop wasting-time-by-applying-clean-code-principles

33,201 views

Published on

Published in: Technology
2 Comments
25 Likes
Statistics
Notes
  • @Choleriker Everything you, may i call it 'shouted', at me is pretty much the point i was trying to make. This was a 45 minute session and with the intro of talking about maintainable code bases I've only picked on of the many examples from clean code and tried to show why it matters. It was tailed to the audience. Also the title of this was 'stop wasting time using the principles laid out in clean code' and not 'clean code is bullshit'.. just to clear that up.

    It's been over a year so I don't really remember all the points but the video is here: http://conference.phpnw.org.uk/phpnw11/schedule/volker-dusch/

    TDD does relate to clean code in the regard that it helps to keep a code base changeable with prevents rot and is something that has been restated so many times by UB and the like that I don't see a point in arguing about it. But since that sentence isn't really English due to all the typos I'm not sure I got the points you where trying to make :)

    p.s. replying and seeing that your nick is 'Choleriker' really cracked me up :) So I guess the title and what the slides said just caught you on the wrong foot and isn't just link spam.

    If you wanna have a longer dicussion about this we can do this over a coffee or beer if you'd like (since we're both in Berlin ;) )
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • That is really not correct what you are writing here. Maybe for PHP or small software projects you can do it without any clean code principles, or if you are not interested in code quality. But the principles of clean code are needed for all developers which wanna have code which are understandable for all. TDD and unit tests does not relate do any principles and needed in all software! Clean code even does not only say something about naming, ITS MUCH MORE! Read clean code again and then think about you have written down here. For me its bullshit to stop thinking about the principles of clean code! If you dont have understand the principles at all for that, you can refer my blog where i write down all principles that are related to clean code, which can be better named as 'High quality code'! http://blog.tecbehind.de
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
33,201
On SlideShare
0
From Embeds
0
Number of Embeds
210
Actions
Shares
0
Downloads
186
Comments
2
Likes
25
Embeds 0
No embeds

No notes for slide

Stop wasting-time-by-applying-clean-code-principles

  1. Stop wasting time through clean code<br />#phpnw11<br />Volker Dusch / @__edorian<br />
  2. So stop wasting time!Why are we here?<br />2<br />Introduction<br />
  3. Why are we hereto talk code and coding!<br />3<br />Introduction<br />
  4. });});});<br />4<br />Introduction<br />
  5. Me?<br />5<br />Introduction<br />
  6. Volker Dusch@__edorian<br />6<br />Introduction<br />
  7. PHP for around 9 years <br />7<br />Introduction<br />
  8. I’m currently into TDD, CI, Clean Code and shipping<br />8<br />Introduction<br />…amongst other stuff<br />
  9. 9<br />Introduction<br />Just go and buy those<br />*Book covers used under fair use<br />
  10. Get in touchTwitter: @__edorianXing / G+: Volker DuschStackoverflow:(visit us at http://chat.stackoverflow.com/rooms/11/phpIRC: edorianMail: php@wallbash.com<br />10<br />Introduction<br />
  11. Ask questions at any time!<br />11<br />Introduction<br />First question gets an elePHPant<br />Another question gets one too!<br />
  12. Why try to save time?<br />12<br />Motivation<br />
  13. Because we are professionals<br />13<br />Motivation<br />
  14. Even so our field is quite young<br />14<br />Motivation<br />
  15. Do you know how expensive it is to have you people around?<br />15<br />Motivation<br />
  16. 16<br />Motivation<br />German numbers, ymmv<br />€, £, Approx.. values<br />You get around 50k a year<br />50.000 / Year<br />Adding non-wage labor costs of around 60%<br />80.000 / Year<br />Office space, water, hardware, coffee, plants, cleaning, drugs, travels, training<br />100.000 / Year<br />250 Days without weekends and holidays and your 30 holidays => 220 days<br />455 / Day<br />When working 8 hours a day<br />55 Bucks per Hour!<br />Your employer expects you to contribute <br />over 100.000 Bucks in business value per year!<br />
  17. Wait why are we here again?<br />17<br />Motivation<br />
  18. To get things done, fast!Delivering is fun!<br />18<br />Motivation<br />
  19. Coding!<br />19<br />Motivation<br />Not:<br /><ul><li>Requirement engineering,
  20. Scrum,
  21. Kanban,
  22. Organizational structures
  23. Sane hardware
  24. Sane working environments
  25. Tools</li></li></ul><li>What do we spent time on?<br />20<br />Time! It matters!<br />
  26. 21<br />Time! It matters!<br />Not programming<br />Changing<br />Planning<br />Thinking<br />Creating<br />Growing/Building<br />Asking<br />Typing<br />Reviewing<br />Reading<br />
  27. We spent time:<br />22<br />Time! It matters!<br /><ul><li>Planning
  28. Reading
  29. Processing
  30. Writing
  31. Going to 10
  32. Actual typing? Not that much</li></li></ul><li>23<br />Time! It matters!<br />Your codebase is just likethe database of a website<br />Read:Write Ratio - 10:1<br />and brains suck as caches<br />
  33. Goals:<br />24<br />Time! It matters!<br /><ul><li>Cheap writes</li></ul>The ability to change your software quickly<br /><ul><li>Cheap reads</li></ul>The ability to understand your software quickly<br /><ul><li>Writes require reads</li></li></ul><li>Cheap reads<br />25<br />Time! It matters!<br />People will read your code!<br />again and again and again and again<br />Does it take 5 minutes or 2 hours to understand a module?<br />
  34. Why is our code hard to understand?<br />26<br />Clean code?<br /><ul><li>Management?
  35. Unclear requirements?
  36. Customers?
  37. Schedules?
  38. Requirement changes?</li></li></ul><li>Because WE wrote it like that!We had our reasons but it still is our responsibility!<br />27<br />Clean code?<br />
  39. Rushing out stuff to make people happy“Well I’d go back and fix it but I can’t because $x”Is that comfortable?<br />28<br />Clean Code?<br />
  40. What we need:<br />29<br />Clean Code?<br />A way to WRITE to our codebase so that we are able to keep our READ costs low!<br /><ul><li>Fearing we might break something leads to us not making necessary changes
  41. If we don‘t fix stuff we won‘t have a readable, maintainable codebase</li></li></ul><li>We need a “Pragmatic” approach for cheap writes<br />30<br />Pragmatic!<br />
  42. Pragmatic?<br />31<br />Pragmatic?<br /><ul><li>Fast
  43. Simple
  44. Just get it done as fast as possible
  45. Nobody cares how you do it
  46. Micromanagement?</li></li></ul><li>‘Pragmatic’ === ‘TDD’<br />32<br />Pragmatic!<br /><ul><li>My boss should NOT have to care about the fact that I’m writing unit tests!
  47. If you can go faster when writing tests it is your responsibility to do that!
  48. When not writing tests means “getting it done slower” nobody is asking you to do it!</li></li></ul><li>I don’t have time to sharpen my axe!There are so many trees that I need to cut down<br />33<br />Pragmatic!<br />
  49. This is not a TDD session<br />34<br />TDD! It saves kittens!<br />A small TDD checklist:<br /><ul><li>Write unit tests! TDD is just the fastest way.
  50. Just do it. It’s not magic, don’t fear doing it!
  51. Practice before you do it at work!
  52. Tools? You don‘t need tools to get started!
  53. watch –n1 'phpunit' and ¼ of a screen is ALL you need!</li></li></ul><li>Get the basics right<br />35<br />TDD! It saves kittens!<br />A small TDD checklist (continued):<br /><ul><li>Aim for 100% test coverage
  54. Your tests should run REALLY fast
  55. If you can‘t do that now use --filter
  56. „You“ means everyone in your team!</li></li></ul><li>Writing unit tests is a SKILL<br />36<br />TDD! It saves kittens!<br /><ul><li>You need to acquire it
  57. You need to practice
  58. It‘s just like any other tool and concept
  59. Actually – Unit testing is really easy…
  60. Writing testable code can be hard and NEEDS practice!</li></li></ul><li>Enough! Code! Now!<br />37<br />Enough! Code! Now!<br />
  61. What to focus on?Is there anything we can base our decisions on? <br />38<br />Enough! Code! Now!<br />
  62. 39<br />I <2 off by one errors<br />"There are only two hard problems in Computer Science: cache invalidation, naming things, and off-by-one errors.“<br />- Phil Karlton<br />
  63. 40<br />Naming things!<br />
  64. We name everythingWe might want to get that right<br />41<br />Naming matters<br />
  65. The purpose of a name is to reveal intent<br />42<br />Naming matters<br />
  66. A good name tells you everything you need to know!<br />43<br />Examples!<br />class User {<br /> public function getId() {…}<br /> public function getName() {…}<br /> /** Calculate Body-Mass-Index @link … */<br /> public function getBMI() {…}<br /> /** @param float $kg Weight in Kilogramm */<br /> public function setWeight($kg) {…}<br />
  67. You shouldn’t need comments<br />44<br />Code! Finally!<br />class User {<br /> public function getUserId() {…}<br /> public function getFirst/Last/DisplayName() {…}<br /> /** @link … */<br /> public function getBodyMassIndex() {…}<br /> /** @param float $kilogramm */<br /> public function setWeight($kilogramm) {…}<br />
  68. Names already arecompliable documentation<br />45<br />Naming matters<br />
  69. Another example<br />46<br />Code!<br />class Calendar {<br /> public function getMonth($shortened = false) {…}<br />}<br />class Calendar {<br /> public function getMonthNames() {…}<br /> public function getShortendMonthNames() {…}<br />}<br />
  70. Proper naming is important and easily neglectable<br />47<br />Names matter! A lot!<br /><ul><li>Descriptive names communicate intent
  71. They enable us to understand what‘s up
  72. Misleading names can make it nearly impossible to navigate in a codebase</li></li></ul><li>It is easy to write code that a machine understands<br />48<br />Names matter! A lot!<br />Writing code that <br />ANOTHER human <br />can understand is <br />A LOT harder<br />
  73. Before we startnaming things<br />49<br />Let’s go!<br />
  74. 50<br />$pszError = &“E_FATAL0“;<br />Hungarian Notation?<br />$aArray; $sString; $fFloat; $cCount;<br />interface IUser {} class CUser {}<br /><ul><li>For classes and interfaces we have IDEs
  75. If you have long methods you might be interested in types 100 lines later
  76. But that isn‘t the issue we want to adress</li></li></ul><li>Let’s name things!<br />51<br />Name everything!<br /><ul><li>Namespaces
  77. Classes
  78. Functions
  79. Variables</li></li></ul><li>Namespaces<br />52<br />Name Namespaces!<br /><ul><li>The things that come before the last underscore of your class name
  80. Namespaces gave us the ability to get pretty class names
  81. “User” vs“Framework_Util_Stuff_Foo_Auth_User”</li></li></ul><li>Namespaces<br />53<br />PSR-0<br /><ul><li>The PHP world has mostly agreed that Namespaces  Directory structure
  82. Is that a good idea?
  83. Harder to change folder structure
  84. Duplicate information
  85. Just do it anyways. It’s the expected layout
  86. It also pushes you to create modules!</li></li></ul><li>Classes<br />54<br />Classy class names<br /><ul><li>The class name is the single most important definition of what fits into that class
  87. Using generic names throws that away!
  88. ApplicationManager, FrameworkDataInformationProvider, UtilityDataProcessor</li></li></ul><li>One class, one purpose<br />55<br />Putting the “S” in “SOLID”<br /><ul><li>Name it after its purpose
  89. Single responsibility principle!
  90. There should be only one reason to change a class</li></li></ul><li>Proper class namesrelate to good methods<br />56<br />It’s good if it doesn’t fit!<br /><ul><li>Think of the real world</li></ul>$user->getStoreDiscount() ?<br />$userManager->getStoreDiscount(); ?<br /><ul><li>Do we ask your customers how much they owe us or do we keep track of that?</li></li></ul><li>Proper class nameslead to smaller classes<br />57<br />Focus!<br /><ul><li>Should we let our Email class figure out attachment mime types?
  91. By always asking if stuff fits we can ‘discover’ new classes in our applications
  92. EmailAttachment, ImageEmailAttachment?</li></li></ul><li>And we care because?<br />58<br />OOP is there to help us!<br /><ul><li>Big classes rob you of all OO benefits
  93. You depend on way to much other stuff
  94. Usually tightly coupled
  95. You can extend from those classes and chances are you can’t swap out ether
  96. You don’t get nice interfaces as well</li></li></ul><li>A logger interface<br />59<br />Loggers log!<br />interface Logger {<br /> public function log($message);<br />}<br />
  97. Composite?<br />60<br />Another logger interface<br />interface Logger {<br /> public function log($logMessage);<br /> public function setOutputFormat($format);<br /> public function activate();<br /> public function deactivate();<br /> public function flush();<br />public function setIncludeTimestamp($format);<br /> public function addLogger(Logger $logger);<br />}<br />
  98. All those rules! I want to get things done!<br />61<br />Rule 1: Segmentation fault<br /><ul><li>These are just guidelines
  99. They exist to act as early warning signs
  100. Not running into trouble two hours later can save an amazing amount of time</li></li></ul><li>62<br />‘Util’ is the new ‘Manager’<br />Util<br /> Because naming is hard<br />django/core/files/utils.py<br />django/core/mail/utils.py<br />django/db/backends/util.py<br />django/db/utils.py<br />django/forms/util.py<br />django/http/utils.py<br />django/test/utils.py<br />django/utils/... (django still rocks btw.)<br />
  101. Reserved class names<br />63<br />Name everything!<br /><ul><li>Some names already have meaning
  102. If you name stuff like a design pattern you’d better implement that pattern!
  103. Factory, DataMapper, Visitor, Composite, Strategy, Builder
  104. A Logger without a log function?</li></li></ul><li>Functions!<br />64<br />Best thing since LJMP<br /><ul><li>First unit of organization
  105. Functions are where the action is
  106. For a long time functions where all we had!
  107. Calling one got A LOT faster over the years</li></li></ul><li>Function naming <br />65<br />Starting off easy<br /><ul><li>Does it return a boolean?
  108. Call it hasX or isX</li></li></ul><li>66<br />Implementation details<br />Hidden booleans<br />$status = $user->getStatus();<br />if($status == $user::STATUS_BANNED) {<br />}<br />if($user->isBanned()) {<br />}<br />
  109. Getters and Setters<br />67<br />setTitle($this);<br /><ul><li>Getter retrieve internal state
  110. Setters modify internal state
  111. Both should not modify anything else
  112. Don’t make your setters into liars</li></li></ul><li>Setters return null!<br />68<br />setTitle($this);<br /><ul><li>Or maybe $this but nothing else!
  113. Return codes for ugly IFs onto consumers</li></ul>if(!$config->set("key", "value")) {<br />}<br />if(!$config->has("key")) { <br /> $config->set("key", "value");<br />}<br />
  114. No boolean parameters<br />69<br />$user->setInactive(false);<br />If you don’t have a very good reason!<br />$user->setAdminStatus(false);<br />$user->setAdminStatus(true);<br />vs<br />$user->revokeAdminRights();<br />$user->grantAdminRights();<br />
  115. Classes are nounsFunctions start with verbs!<br />70<br />$title->show();<br />->createStuff(); ->deleteStuff()<br />->dispatchCall(); ->subscribeUser();<br />But never<br />$user->admin(); or $user->bag();<br />$list->subscription();<br />
  116. Agree on verbs for actions<br />71<br />Don’t $dir->expunge();<br />Can you tell me the difference between<br />$directory->delete($entry);<br />And<br />$directory->remove($entry);<br />$router->dispatch/delegate($call);<br />$list->add/append($user);<br />
  117. Different actions need distinguishable names!<br />72<br />Name everything!<br /><ul><li>Even if that makes function names longer
  118. Usually those functions get created for “doing the same thing with different side effects but not wanting to call it that”
  119. If you can agree upon terms with your team that’s even better!</li></li></ul><li>Always favor longfunction names? NO!<br />73<br />Bigger is not always better<br /><ul><li>A short an precise public API is important
  120. But you want long privates</li></ul>$user->setUserNameOrThrowAn<br />ExceptionIfNotPossible();<br />$logger->logMessageButDontDoAnything<br />IfYouCantWriteToTheBackend($message);<br />Not even: $logger->logMessage($message);<br />
  121. Public functions will be called from many places<br />74<br />Bigger is not always better<br /><ul><li>They should do one thing and maybe throw an exception if they can’t do that
  122. Precise names help a lot to create readable client code
  123. Interally used functions can be named as verbosely as needed to communicate their intent and behavior</li></li></ul><li>Readable classes<br />75<br />implements humanReadable<br /><ul><li>Some state
  124. Lots of small descripte methods
  125. Best arranged in order of interest</li></li></ul><li>Readable classes?<br />76<br />sort($this) stuff out<br />class Log {<br /> private function writeToLogfile() {}<br /> private functionflushCurrentLogfileBuffer(){}<br /> public function log($message) {}<br /> private isLogfileWriteable {}<br /> public function __construct(<br />SplFileInfo$logfile) {}<br /> private createLogfileIfNecaccary() {}<br />}<br />
  126. Method ordering<br />77<br />usort<br /><ul><li>First you build it
  127. Then you call it
  128. Then it does work
  129. Most work happens in private methods</li></li></ul><li>Readable classes?<br />78<br />sorted($this)<br />class Log {<br /> public function __construct(<br />SplFileInfo$logfile) {}<br /> public functionlog {}<br /> private functionisLogfileWriteable{}<br /> private function createLogfileIfNecaccary() {}<br /> private functionwriteToLogfile() {}<br /> private functionflushCurrentLogfileBuffer(){}<br />} <br />
  130. Maximum function length?<br />79<br />Name everything!<br /><ul><li>LOC of the function body?
  131. Do you like reading functions that are:
  132. Over 100?
  133. Over 20?
  134. Over 7?</li></li></ul><li>6 Lines ought to be enough for everybody?<br />80<br />Reading > Writing<br /><ul><li>Doing only one thing shouldn’t take much space
  135. Can you split it into 2 functions? Then do!</li></li></ul><li>Why long function bodies?<br />81<br />main::doEverything();<br /><ul><li>It’s very easy to create
  136. You just have to worry about functionality</li></li></ul><li>82<br />Implements unreadable<br />What is in a long function?<br />Local variables and code that operates on them<br />public function log($message) {<br />$log = ''; $errors = array();<br /> $log .= PHP_EOL . date('Y-m-d H:i:s') . ': ';<br /> if(!$message) { $errrors[] = 'No Message'; }<br /> else { $log .= $message; }<br /> if(!fwrite($this->log)) { <br /> $errors[] = 'Write E'; }<br /> return $errros;<br />} // I'm really 12 lines long <br />
  137. State and operations?<br />83<br />We shall name it Timmy<br /><ul><li>It’s a class!
  138. Long functions are where classes hide!
  139. Even in the small example we could separate Log and LogFileWriter</li></li></ul><li>Function overflow?<br />84<br />Methods everywhere!<br /><ul><li>Good thing they have nice names
  140. And we wrapped them in classes
  141. That’s good for read/write access!
  142. Calling overhead just doesn’t matter</li></li></ul><li>Smaller is harder to write<br />85<br />Wrapping up functions<br /><ul><li>Writing ONLY small functions is a SKILL
  143. It is _easy_ to write big functions!
  144. But the first change makes it all worth!
  145. If everything is small it can even impact your coding standard ;) </li></li></ul><li>Last thing to nameVariables<br />86<br />We shall name it Timmy<br /><ul><li>Rules of thumb:
  146. Descriptive function parameters
  147. Big scope: long name
  148. Short scope: short name</li></li></ul><li>Example<br />87<br />We shall name it Timmy<br />for($i = 7; $i; --$i) {<br /> $this->doStuffToListItem($i);<br />} <br />vs<br />for($currentlistItemId = 7;<br /> $this->listItemIdIsValid($currentlistItemId);<br /> --$currentlistItemId<br />) {<br /> $this->doStuffToListItem($currentlistItemId);<br />}<br />
  149. Descriptive members!<br />88<br />Class scope is big scope!<br /><ul><li>Members are accessed from many places. They should communicate intent!
  150. Local variables in small classes are only used twice or thrice</li></li></ul><li>89<br />Names matter<br />Wrapping up naming<br />"A name, like an honorable human, should say what it means <br />and mean what it says”<br />Names are the basis of our communication!<br />They can speed up things immensely!<br />
  151. Arrays<br />90<br />Hash maps everywhere!<br /><ul><li>PHP is hash-map-based programming
  152. Our constants are in hash maps
  153. Our variables are stored in hash maps
  154. Our arrays are hash maps
  155. We’d like to foreach over EVERYTHING!
  156. PHP Arrays let us create amazing data structures without much hassle</li></li></ul><li>91<br />Hash maps everywhere!<br />Arrays<br />PHP Arrays also let us create amazingly complex and unmaintainable data structures<br />$config['user'][$user['id']]['languages']['native'] ='en';<br />But arrays are too way cool to not use them.<br />Maybe we need OOP arrays?<br />
  157. Value objects!<br />92<br />New them everywhere!<br /><ul><li>OOP Data structure
  158. You can create them everywhere, no DI!
  159. Identity is based on state! Think dates
  160. Lots of getters and setters. If you really miss arrays you can implement ArrayAccess too
  161. http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/
  162. Implement IteratorAggregate and you get foreach back!</li></li></ul><li>Value objects!<br />93<br />New them everywhere!<br />class List implementsIteratorAggregate, Countable {<br />protected $list= array();<br />publicfunctionadd($item) { $this->list[] = $item; }<br />publicfunctioncount() { returncount($this->list); }<br />publicfunctionremove($index) { <br />if(!isset($this->list[$index])) {/* throw ... */ }<br />unset($this->list[$index]);<br /> $this->list = array_merge($this->list); }<br />publicfunctiongetIterator { <br />returnnewArrayIterator($this->list); }<br />}<br />
  163. Value objects!<br />94<br />New them everywhere!<br />classMonthlyValuesimplementsIteratorAggregate {<br />protected $values = array();<br />publicfunction __construct(array $values) {<br />if(count($values) != 12) {<br />thrownnewInvalidArgumentException('...');<br /> }<br /> $this->values = $values;<br /> }<br />publicfunctionget($monthNumber) {<br />return $this->values[$monthNumber];<br /> } // ... publicfunctiongetIterator() ...<br />
  164. Comments<br />95<br />New them everywhere!<br /><ul><li>I’ve spend a lot of time ranting about comments. Name things properly!
  165. Additional Resources:
  166. http://edorian.posterous.com/they-told-you-to-document-everything-they-lie
  167. http://www.slideshare.net/Edorian/php-unconference-europa-clean-code-stop-wasting-my-time</li></li></ul><li>96<br />Find the method body<br />abstract class xyzRequest {<br /> /**<br /> * Initializes this xyzRequest.<br /> *<br /> * Available options:<br /> *<br /> * * logging: Whether to enable logging or not (false by default)<br /> *<br /> * @param xyzEventDispatcher $dispatcher An xyzEventDispatcher instance<br /> * @param array $parameters An associative array of initialization parameters<br /> * @param array $attributes An associative array of initialization attributes<br /> * @param array $options An associative array of options<br /> *<br /> * @return bool true, if initialization completes successfully, otherwise false<br /> *<br /> * @throws <b>xyzInitializationException</b> If an error occurs while initializing this xyzRequest<br /> */<br /> public function initialize(xyzEventDispatcher $dispatcher, $parameters = array(), $attributes = array(), $options = array()) {<br />
  168. 97<br />Name everything!<br />Error handling<br /><ul><li>Is a big topic that requires a whole session
  169. Just use exceptions in favor of return codes
  170. Don‘t force IF statements in client code</li></ul>$stuff = $factory->create('unkownStuff');<br />$stuff->doWork(); // E_FATAL?<br /><ul><li>Enable consumers to handle errors at a point of their choosing!</li></li></ul><li>Professionals deliver!<br />98<br />Wrapping up<br /><ul><li>Testing makes you FASTER!
  171. Creating small functions saves time!
  172. Only tested code can stay clean
  173. You can only fix what you can reproduce!
  174. JUST – DO – IT! YOU – ARE – IN – CHARGE!</li></li></ul><li>99<br />Thanks a lot!<br />Thank you for your time!<br />Slides will be at: http://joind.in/3600<br />the link will be at @__edorian too ;)<br />Please leave me feedback!<br />
  175. Recommended Reads<br />100<br />Rate my talk please!<br />Clean Code<br />Management<br />Professionalism<br />^ Best book in 2011 ^<br />In my humble opinion<br />*Book covers used under fair use<br />More:<br />https://www.google.com/bookmarks/l#!threadID=GU46RJYjEsMU%2FBDcqV3woQ8eH4sOcl<br />

×