Professional-grade
software design
whoami
Brian Fenton
@brianfenton
www.brianfenton.us
Mashery, an Intel Company
Overview
Testing
SOLID
Object
calisthenics
Smells
Patterns
Resources
Professional
Jeff Hebert, HeroMachine.com
Tests!
If you don’t have tests, you’re
REWRITING, not refactoring
Tests first
Testing after you write the class won’t
improve your design
More likely to just test the
implementation (low v...
Unit testing forces you to
feel the pain of bad design
up front
Some smells exposed by
tests
Lots of dependencies
Class/method does too much
Requires lots of setup to do anything
Class i...
SOLID
S – Single Responsibility
O – Open/Closed
L – Liskov Substitution
I – Interface Segregation
D – Dependency Inversion...
Single Responsibility Principle
<?php
class Person extends Model {
public $name;
public $birthDate;
protected $preferences;
public function getPreferences...
<?php
class Person extends Model {
public $name;
public $birthDate;
protected $preferences;
public function getPreferences...
Open/Closed Principle
Open for extension
Closed for modification
The OCP litmus test
Can you add/change a feature by only adding new
classes?
Also allowed to update
Controllers
Configurat...
Liskov Substitution
Principle
“objects in a program should be
replaceable with instances of their
subtypes without alterin...
abstract class Shape{
public function getHeight();
public function setHeight($height);
public function getLength();
public...
class Square extends Shape{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight...
class Rectangle extends Shape{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}...
Interface Segregation Principle
Dependency Inversion
Principle
“Higher level modules should not depend
on lower level modules”
TL;DR
mysqli_query() BAD
DataStore->query() GOOD
Naming
Care about your names
(class/method/variable)
If it’s hard to name something, it means
you can’t describe it succin...
Good Naming
Don’t abbreviate
Don’t be afraid to be verbose
Suspect Names
Classes
Manager
Handler
Methods
Process
“And”
Comments
Use inline comments sparingly
Do use docblocks though
TODOs
Tend to rot and never get fixed
If you use a TODO, add a ticket number
A well-named method that
communicates intent is far
more valuable than a comment
Methods
You can (almost) always make a
method smaller
Pay attention to your execution path
Check your CRAP index with phpm...
Cognitive load
Declare variables as close to when they
will be used as possible
If you can pass data from method to
method...
Source order
Declare methods in the order they’re
called
Public to private
Avoid “magic” values
DRY
Single source of truth
Self-documenting
Which of these is easier to
understand?
json_last_error() == 5;
json_last_error() == JSON_ERROR_UTF8;
$length > 1024
$leng...
Not what I mean!
unsigned three = 1;
unsigned five = 5;
unsigned seven = 7;
https://github.com/torvalds/linux/blob/d158fc7...
“2 is a code smell”
- Alex Miller
Dependency Injection
Pass external dependencies into objects
Constructor injection
Setter injection
Potential smell: too m...
Object Calisthenics
(briefly)
No more than one level of
indentation per method
public function processData($data) {
$newData = array();
$count = 1;
foreach ($data as $row) {
if (!$row) {
continue;
}
if...
…
foreach ($data as $row) {
// skip empty rows
if (!$row) {
continue;
}
if ($count === 1) {
$newData[] = implode(',', arra...
public function processData($data) {
$newData = array();
$count = 1;
$data = $this->filterEmptyRows($data);
foreach ($data...
public function processData($data) {
$newData = array();
$count = 1;
$data = $this->filterEmptyRows($data);
foreach ($data...
public function processData($data) {
$newData = array();
$headersOnly = true;
$data = $this->filterEmptyRows($data);
forea...
Interlude… which is
clearer?
$this->setActive(true);
$this->setActive(false);
OR
$this->activate();
$this->deactivate();
public function processRow($row,
$headersOnly) {
if ($headersOnly === true) {
return $this->getHeaderRow($row);
} else {
r...
public function processRow($row,
$headersOnly) {
if ($headersOnly === true) {
return $this->getHeaderRow($row);
} else {
r...
public function processData($data) {
$newData = array();
$data = $this->filterEmptyRows($data);
$firstRow= array_pop($data...
public function transformToCsv($data) {
$data = $this->filterEmptyRows($data);
$firstRow= array_pop($data);
$csv = array()...
public function processData($data) {
$newData = array();
$count = 1;
foreach ($data as $row) {
if (!$row) {
continue;
}
if...
Don’t use else
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {...
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
retu...
Command-Query
Separation
Complete separation between questions
and commands
“Asking a question shouldn’t change the
answer”
public function getUser($id) {
$user = $this->dataStore->fetchUser($id);
if (!$user) {
$user = new User(array($id));
}
ret...
public function getUser($id) {
return ($this->dataStore->fetchUser($id) ?: null;
}
The final secret…
OOP is all about message
passing and behaviors
It’s not about inheritance
It’s not about code reuse
Favor composition over...
User
Event
Listener
Mailer
"Send
Notification"
w/User data
So decouple!
Such architecture
So amaze
Wow
Much message
Summary
Write small objects
Write tiny methods
Strive for good names
Seek loose coupling
Focus on message passing
Treat ob...
Resources - Presentations
The Clean Code Talks -- Inheritance,
Polymorphism, & Testing
Object Calisthenics
Resources – Books
Code Complete: A Practical Handbook of
Software Construction, Second Edition
Growing Object-Oriented Sof...
Questions?
https://joind.in/10562
Upcoming SlideShare
Loading in...5
×

Professional-grade software design

1,036

Published on

Talk given at MidwestPHP 2014 about guidelines for SOLID object-oriented design

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
1,036
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
8
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • Can everyone hear me? My name is Brian Fenton and I’ll be talking about Software Design. I’ll leave some time for questions at the end, but if you have a question during the talk or think I’m going too quickly, feel free to raise your hand, start shouting obscenities, throw a shoe at me, whatever. If I don’t know the answer come find me afterwards and we’ll look it up, so that way we both learn something. Let’s get started.
  • Just a little bit of information about me and where you can find me. I work at Mashery, and we are, of course, hiring.
  • So during this talk I’m going to go over testing, a fair bit on SOLID and object calisthenics. Most of the smells and patterns stuff will be interspersed with other sections, and I’m not really talking much about the resources other than to provide some avenues for further learning.
  • This talk is NOT on how to be arockstar cowboy ninja coder, this is how to produce clean, maintainable software that’s easy to modify and maintain over months and years of use. If you’re here to write crappy startup code that you can keep afloat by throwing more AWS instances at it until you get bought, this isn’t the talk for you. Being a professional is not easy. It takes a significant amount of discipline. It involves refactoring by the book and only updating the code via small, repeatable, verifiable steps. As soon as you start a refactoring, you’re not changing anything other than that tiny piece anymore until you’re done. One change at a time.Being professional also involves sticking to your principles. It means taking the time to verify functionality, writing the tests, writing good commit messages, even when you’re under deadline pressure. The real measure of someone’s professionalism is whether or not they stick with the best practices even when it’s not convenient.
  • This talk is about design, but a lot of the things I’ll mention have to do with testing as well. Testing will be a minor running theme throughout the talk, but since there are two or three other testing-related talks at this event I’ll leave the deep discussion on it to them. You can have the greatest design in the world, but if it the code doesn’t actually do what it’s intended to do, it’s at best useless, or at worst outright dangerous. Suffice to say for now that tests are very important and they can be a great tool to influence, verify, and safely evolve your design.
  • Making these changes in a system that’s already been released is like operating without a safety net, unless you have good test coverage to protect you from making unnoticed mistakes. If you can’t cheaply prove that the system behaves one way before you start, how can you show that it still behaves that way when you’re done? If you’re calling what you’re doing refactoring but you don’t have tests, you’re just lying to yourself.
  • If you’ve had the chance to see a set of tests written before the code and one written after the code was finished, they’re strikingly different. The “after” tests are much more concerned with the implementation details of the class and making sure they have good line coverage, whereas the “before” tests are more about verifying the desired external behavior. That’s really what we care about with unit tests anyway, is making sure the class exhibits the right behavior. Implementation-focused tests actually make refactoring harder because they break if the internals of the classes change, and you’ve just cost yourself the information hiding benefits of OOP.
  • When you’re writing unit tests, you’re forcing yourself to actually use the class to accomplish things. If you write tests at the end, or worse yet, just chuck the code over the wall for QA or whoever to write tests, you don’t get any feedback about how the class actually behaves. If you’re writing tests, and the class is a real pain in the ass to use, you’re going to find out while you’re writing it, which is about the cheapest time to fix it.
  • If a class is hard to test, it’s a design flaw. Different flaws manifest themselves in different ways, though. If you have to do a ton of mocking, your class probably has too many dependencies, or your methods are doing too much. The more setup you have to do for each test, the more likely it is that your methods are doing too much. If you have to write really convoluted test scenarios in order to exercise behavior, the class’s methods are probably doing too much. If you have to dig inside a bunch of private methods and state to test things, maybe there’s another class trying to get out. Unit testing is very good at exposing “iceberg classes” where 80% of what the class does is hidden away in protected or private code. I used to be a big fan of making as much as possible protected, but now I realized I was just making my individual classes responsible for too much.
  • I’m going to list these fairly quickly but don’t worry, I have a number of overly contrived examples at the ready
  • Classes should have only one responsibility. I’d call this one of the most important of the five principles. You see in this picture the cat appears to be doing an excellent job of catching rats, but that’s all he’s doing. And that’s totally fine. This fits nicely with the Unix philosophy of lots of small tools, doing one thing well. Classes that only do one thing are much easier to test and debug, and they are less likely to surprise you. You don’t want a method call to a Validator class updating db records.
  • So this is a pretty basic entity model. One of these things doesn’t belong here though. A model’s only responsibility should be behavior related to the entity it’s representing, it shouldn’t be responsible for persisting itself. Yes, this makes ActiveRecord a suboptimal pattern, and I’ll happily rant about that later if anyone wants to listen.
  • This is better. The Person model is back to only doing one thing, and the save behavior has been moved to a persistence object instead. Note also that I only type hinted on Model, not Person. I’ll come back to that when we get to the L part of SOLID in a few slides.
  • I’m not actually going to do a cat for all of these, sorry. I thought about it but it just didn’t work out. Anyway, the open closed principle states that an object should be OPEN for extension but CLOSED for modification. That’s easy to say, but what does it mean?
  • There’s an awesome test for this that pretty well sums up what this principle is about: think of a feature to implement, probably the most recent one you worked on or are working on. Can you implement that feature in your codebase SOLELY by adding new classes and not changing any existing classes in your system? Your configuration and wiring code gets a bit of a pass, but in most systems this is surprisingly difficult. You have to rely a lot on polymorphic dispatch and most codebases just aren’t set up for that. If you’re interested in that there’s a good Google talk up on youtube about writing code without Ifs that digs into it further.I’m generally a big fan of class constants but this is one place that they can bite you. If you’re using a constant value you can’t override it in a child class, so if there’s any chance it might actually vary, use a regular property instead.
  • Objects of a certain type or subtype should be transparently swappable. Named after Barbara Liskov. A very simple example of this is with shapes
  • This is going to represent our basic four-sided shape. Nothing fancy here.
  • Here’s our first shape, the Square. Pretty straightforward shape, right? You can assume that there’s a constructor where we set the dimensions, but you see here from this implementation that the length and height are always going to be the same. Squares are just like that.
  • So here we have a different shape. Still has the same method signatures, it’s still a four sided shape, but what if we start trying to use them in place of one another? Now all of a sudden if we change the height of our Shape, we can no longer assume that the length of our shape will match. We’ve violated the contract that we had with the user when we gave them our Square shape. This is a textbook example of a violation of the LSP and we need this type of a principle in place to make the best use of a type system. Even duck typing won’t tell us if the underlying behavior is different, and since we can’t know that without seeing it break, it’s best to make sure that it isn’t different in the first place.
  • Back to cats. This principle says to favor many small, fine grained interfaces vs. one large one. Interfaces should be based on behavior rather than “it’s one of these classes”. Think of interfaces that come with PHP. Traversable, Countable, Serializable, things like that. They advertise capabilities that the object possesses, not what it inherits from. So keep your interfaces small. You don’t want an interface to have 30 methods on it, 3 is a much better goal.
  • You’ve probably heard about this in other places that talked about Dependency Injection, but Dependency Inversion and Dependency Injection aren’t quite the same thing. Dependency inversion is really just a way of saying that you should depend on abstractions in your system and not on its details. Now what does that mean to you on a day to day basis?
  • The core of this principle is actually about abstractions. It’s more about saying “use a database adapter” instead of depending on direct calls to things like mysql_query. If you’re directly using mysql_query in half your classes then you’re tying everything directly to your database. Nothing for or against MySQL here, but if you are using mysql_query, that type of low level detail should be hidden away in only one place and then that functionality should be exposed via a generic wrapper. Now I know this is kind of a hackneyed example if you think about it, because the number of times you’re going to actually completely change your database engine after your product is in production are very, very low. I picked it because I figured people would be familiar with the idea from their own code. Also, even if you have a database that you know you’re sticking with, that more abstract wrapper object allows you to fix bugs, change behavior, or implement features that you wish your chosen database had. It also makes unit testing possible where low level calls wouldn’t.
  • Naming is a leading indicator of poor design in kind of the same way unit testing can be. If you’re having a hard time naming a thing then there’s a decent chance you don’t truly understand it. Names are like magic… if you know something’s true name, then you have power over it. If you really can’t come up with a name for something, try splitting it into smaller pieces. Grab a thesaurus. Grab a coworker. Run them through the method and ask them what they’d call it. The best names are often a collaborative effort.
  • These are really just two ways of saying the same thing. Use as many characters as you need to express the concept. There’s no excuse to have a variable named $x unless you’re plotting coordinates. Unless you’re using a loop counter or something where there’s a well-known pattern to follow, single character variables are just unhelpful. If you’re worried about typing out long variable names all the time, realize that you’re probably only going to type a few characters of it before autocomplete kicks in. I’m not going to say that all of these are 100% bad all the time, but they’re kind of the lazy way out. And not the good kind of programmer lazy that we all value, the regular lazy that gets you into trouble down the road.
  • These are just a few examples, there are plenty more, but the usually follow the same patterns. Manager is really too generic most of the time. Handler is the same way. Think of what the class actually does, you may really want something like Converter or Transformer or Storage or Parser. A lot of the time if you have something like a UserManager, that class is really a controller instead. “Process” is kind of seductive because it sounds like it’s a very succinct, clear way to describe something, but the problem is it’s still doesn’t impart useful meaning. Think about an unfortunately common method name like “processData”. What does that method called really do? I mean, sure, it processes data, but how? What does it actually do to it? What does the data look like when it’s done? Is “data” the most specific we can be? “And” is an easy one, that almost always means a method is doing too much. I do make an exception with And if you’re naming test methods though.
  • The best reason to use a comment that I can think of is if you did something unintuitive but valid, like unrolling a loop for performance reasons. That’s the type of information you can’t really encapsulate into a named method call very easily. Another thing I see occasionally is a comment like “doA must be called before doB”. That’s an example of Temporal Coupling, and if that’s the case, just wrap both of those calls up into a new method. That’s another place you might consider using “and” in a method name. Tiny methods grouped together into a progression also opens up a pattern called “Template Method”. What you have there is a method that does one discrete thing, but you pull out a tiny portion of it into its own method just so a child class can change its behavior.
  • I like putting todos in the code, but I’m not always very good at following up on them right away. If that’s the case, all it’s really doing is adding noise. If you do use todo comments, make sure to reference a ticket in an issue tracker. That way you know when the comment has “expired”, or if it’s already been done, it provides a very concise pointer to context surrounding a decision.
  • When you see inlinecomments in your code, think about whether you can just replace the commented code with a method that makes the comment unnecessary. Comments rot much faster than code does. If your code needs that many comments, refactor it until its purpose and meaning is self-apparent.
  • I’m not going to read this to you but keep your methods simple, ideally linear, with as little branching as possible. Fewer branches means easier testing. The only time I really wouldn’t add a new method is if all you’re doing is calling a known and well-understood PHP function inside of it. I wouldn’t write a method to increment an integer, or reverse a string, UNLESS adding that method allowed me to clarify the purpose behind the call. If what I was doing was formatting a string so it could be read in a mirror, I’d probably make a method for that, even if all I was doing was reversing a string, because it tells you why. You can always learn what’s happening in a piece of code if you dig enough, but the why is much more important. If all you have is what the code does, you don’t know if what it’s doing is actually correct or a bug. The reason that second almost is up there is because after a certain point you probably want to make a new collaborator object that handles part of the class’s behavior. Having a ton of methods is a pretty good sign that the class is doing too much.
  • Don’t always declare all your variables at the top. This isn’t JavaScript, they won’t be hoisted. If you declare a variable at the top of your method and only use it near the bottom, your brain has to keep track of whatever happens to that variable throughout the entire method instead of just for a line or two at the end. Make things easier on yourself and just declare things right when you need them.That second bullet is a bit of a functional programming thing, but it’s just another way of avoiding dealing with system state if you don’t have to. Only add intermediate variables if the syntax requires it like preg_match, or if it makes the intent of the code more clear
  • I’m advocating for readable code here. I want to read source like you’d read a novel: a method, then methods that are called within it, then the next method, etc… organize your code into paragraphs of related methods, large to small, public to private. If you have to jump back and forth all over a class to trace the execution, take a few minutes and reorder things. It makes for ugly diffs, but it’s easy to maintain once it’s done.
  • So when I talk about a magic value here, what I mean is a string or integer that has a special meaning. If you ever have to change one of those values, you’re stuck hunting all the way through your code base trying to find all the places you use those values and hope you don’t miss any or have a false positive and change something by mistake. Instead you just have a pointer that references that single source of truth and you can update them all in one go.
  • I prefer class constants here instead of global constants, because there are fewer risks of name collisions and there’s automatic context provided by attaching the value to an object. Pay special attention to the last one because that’s particularly tricky to figure out what the author meant without the constant. Is it actually a limit of zero, meaning whatever that is rejects everything, or does it mean there is no limit at all? With the constant that becomes immediately apparent.
  • This can still go wrong. This is actual code in the ext4 filesystem. I don’t want to be the person who has to debug that.
  • Any time you see an integer in your codebase other than 0 or 1, you should stop and think about it. Does it have special meaning? Are you just doing math? In the vast majority of systems, the only meaningful numbers are 0, 1, and “many”. If you need more conceptual values than that, you should probably give them names.
  • More dependencies usually mean the class is too large or doing too much. If your class has 4 or 5 dependencies, take a good look and see if all of them are being used and if you shouldn’t extract that behavior to a collaborator object instead. Say you have a User object that, as part of its job, triggers a notification email when someone registers. Now conceivably you could have a mail message object, a mime type object, a template renderer, a mail transport object, or maybe a queue of some kind. Now whatever sort of structure you have set up for your emails, there’s no reason why that original User object needs to have all of those objects as dependencies. It just wants to send a notification, it shouldn’t care about the template renderer, the underlying mail transport, any of that, it should just have the ability to queue up a notification.
  • This is the topic of a presentation that Raphael Dohms has given a number of times in different venues. I’m not here to re-give his presentation on that, so I’m going to just cover a the first two guidelines to practice. They’re easy to remember, provide good value, and aren’t touched on as much elsewhere in this talk.
  • This is a helpful way to think about decomposing methods into smaller chunks, leaving you with code that’s clearer and more self-documenting. The more levels of indentation you have, the more the method is doing and the more state you have to keep track of in your head while you’re working with it. I’m also not saying you shouldn’t indent the code to show it’s part of a method, that one’s free.
  • Ok, so here we have a relatively straightforward piece of code. It should be pretty clear what this method does after a read or two but it can definitely be made more clear. A quick count shows we have two levels of indentation inside the method, one on the foreach and a second one in a few other places inside it. So let’s just look at the code inside the foreach to start. The first thing we’re doing is skipping over empty rows, and for that we don’t even need to be part of the loop at all, we can just use an existing built-in PHP function.
  • The first thing we’re doing is skipping over empty rows, and for that we don’t even need to be part of the loop at all, we can just use an existing built-in PHP function.
  • So here we’ve removed one area where we have a second level of nesting, and we have a clearly named method that explains exactly what’s going on. There is nothing wrong with a one line method, and now we have an easy place to change if we need to update the filtering logic
  • In this step we’ve taken the inner block of the foreach and extracted it into its own method. We now fit our rule about only one level of nesting, but it’s pretty obvious here that count parameter isn’t much more than a boolean flag
  • Here we renamed the $count parameter to the more fitting $headersOnly and made it a real boolean. Note that none of these changes have yet changed any of the behavior of the system. At each step we’ve maintained consistent output, and each step continues to make the intent clearer and the logical path simpler.However, passing a boolean flag to a method is generally a code smell.
  • Many times when you’re dealing with a boolean flag to a method there’s a clearer way to represent it. Whenever I see a boolean flag being passed to a method I always suspect that it should really be two methods. It’s either toggling some sort of state like the example above, or it’s delegating to two different code paths internally and they can be split out. I’m not saying you can’t still have a setActive private method to handle those transitions, but don’t make it part of the class’s public API
  • So now that we’ve gotten that out of the way, let’s get rid of that boolean parameter. We can do an Extract Method refactor on part of processRow, and now we’ve added another descriptive name.
  • We can another Extract Method on the other half lets us nets us another descriptive name…
  • We now no longer get much use out of that processRow function, so let’s get rid of it and call those two new methods we created instead. In doing that, the smelly boolean flag has been removed. To do that we have to extract the logic to process the first row separately out of the loop. Remember we’re running our tests after every one of these changes so we have no breakages.
  • Now we’re pretty much finished. We’ve renamed the original method and the newData parameter to make it clear that we’re talking about CSVs, and we’ve reduced our cognitive load by clustering the code into related sections and defining our variables close to their first use. All these new methods that we created are simple to test, and the original method became less complex and easier to follow.
  • Just a step back to show what we started with. It’s now a lot more clear what the code is supposed to do
  • This really deals with two main ideas. The first one is multiple return statements from a method. If you have enough information do make a decision about the method’s result, go ahead and return it then. The second is an idea known as Guard Clauses. These are basically validation checks combined with early returns, usually near the top of a method. Let me show you what I mean.
  • So this is pretty straightforward again, it adds 3 ints together and returns the result or null if any of the parameters are not an int. Ignoring the fact that we could combine all those checks onto a single line with AND operators, I think you can see how the nested if/else structure makes the code harder to follow. Now look at this example instead.
  • To me this example is much easier to follow. Here we’re using guard clauses to verify our initial assertions about the parameters we’re passing and immediately exiting the method if they don’t pass. We also no longer have the intermediate variable to track the sum all the way through the method. In this case we’ve verified that we’re already on the happy path and we can just do what we came here to do. Again we could just do all those checks in one IF but the same principle applies.
  • This is one of those practices that takes real discipline, but over time can avoid a large number of bugs. The basic idea is to make a hard separation between any methods that query the system for data in any way, from simple object getters to database queries to API calls, and methods that send commands to the system to change its state in some way.Another way of looking at it is in terms of HTTP semantics. Queries are your GET calls, they should be idempotent. Other calls can change the system.
  • So this is a typical example that you might see that violates the principles of CQS. We’re attempting to get a user by ID, but if that user doesn’t exist in the system, we’re modifying the system to create a new user. In this case you can just allow the getUser method to return null and check that in the calling code. Move the new user creation there if it’s necessary.
  • This kind of relates to one of the guidelines for API design too… be permissive in what you accept, but strict in what you return. If this was a GET call to a /users endpoint, we wouldn’t expect a formerly non-existent user to come back, we’d expect an empty response or a 404 or something. The more you design your system as discrete services communicating with each other over clearly-defined boundaries, the easier it will be to test and modify. It IS possible to go a little overboard with CQS. Entire frameworks exist for this that define Command objects, Query objects, and set up very strict boundaries between different sections. That’s going to be overkill for all but the largest projects. I know we’re not building Java here, I just bring up this pattern to use as another good guideline for design. It will save you from bugs introduced when the system state changes unexpectedly. You may notice me saying this a lot. State is where bugs live.
  • Are you all ready? This is the big one…
  • A final theme I wanted to be sure and mention is around the fundamental idea of object-oriented programming. Most of us start out thinking that objects are a way of getting code reuse without copy and paste. That’s fine, and that’s one purpose that objects serve. That’s not the whole picture though, and thinking about them in that way makes inheritance the major focus. Objects are more than just a combination of state and behavior. Try to treat your objects like they’re each an external computer that you can only communicate with in very specific ways. You’ll be amazed at how tightly coupled your systems suddenly appear if you start to think this way, and how flexible they can really be.That earlier example with a User object sending a notification? What if we didn’t even consider it sending the notification itself, and instead we thought about it in terms of messaging. What would that look like?
  • So as you can see, in this example, there are a lot of objects in play, but more importantly, none of them really have any knowledge or care about any of the others. Each one only has one main purpose, and it receives messages and exhibits behavior based on that. If this looks suspiciously like JavaScript, keep in mind that there are tools like this for PHP as well, such as the Symfony2 EventListener. Just because one language did something right doesn’t mean you can’t import that behavior into a different language.
  • Professional-grade software design

    1. 1. Professional-grade software design
    2. 2. whoami Brian Fenton @brianfenton www.brianfenton.us Mashery, an Intel Company
    3. 3. Overview Testing SOLID Object calisthenics Smells Patterns Resources
    4. 4. Professional Jeff Hebert, HeroMachine.com
    5. 5. Tests!
    6. 6. If you don’t have tests, you’re REWRITING, not refactoring
    7. 7. Tests first Testing after you write the class won’t improve your design More likely to just test the implementation (low value, brittle tests)
    8. 8. Unit testing forces you to feel the pain of bad design up front
    9. 9. Some smells exposed by tests Lots of dependencies Class/method does too much Requires lots of setup to do anything Class is too coupled to its environment Lots of protected/private methods Likely another class worth of behavior hidden inside
    10. 10. SOLID S – Single Responsibility O – Open/Closed L – Liskov Substitution I – Interface Segregation D – Dependency Inversion Because no one can pronounce SRPOCPLSPISPDIP
    11. 11. Single Responsibility Principle
    12. 12. <?php class Person extends Model { public $name; public $birthDate; protected $preferences; public function getPreferences() {} public function save() {} }
    13. 13. <?php class Person extends Model { public $name; public $birthDate; protected $preferences; public function getPreferences() {} } class DataStore public function save(Model $model) {} }
    14. 14. Open/Closed Principle Open for extension Closed for modification
    15. 15. The OCP litmus test Can you add/change a feature by only adding new classes? Also allowed to update Controllers Configuration Templates
    16. 16. Liskov Substitution Principle “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
    17. 17. abstract class Shape{ public function getHeight(); public function setHeight($height); public function getLength(); public function setLength($length); }
    18. 18. class Square extends Shape{ protected $size; public function getHeight() { return $this->size; } public function setHeight($height) { $this->size = $height; } public function getLength() { return $this->size; } public function setLength($length) { $this->size = $length; } }
    19. 19. class Rectangle extends Shape{ protected $height; protected $length; public function getHeight() { return $this->height; } public function setHeight($height) { $this->height= $height; } public function getLength() { return $this->length; } public function setLength($length) { $this->length= $length; } }
    20. 20. Interface Segregation Principle
    21. 21. Dependency Inversion Principle “Higher level modules should not depend on lower level modules”
    22. 22. TL;DR mysqli_query() BAD DataStore->query() GOOD
    23. 23. Naming Care about your names (class/method/variable) If it’s hard to name something, it means you can’t describe it succinctly If you can’t describe what it does, it does too much or you don’t understand what it does
    24. 24. Good Naming Don’t abbreviate Don’t be afraid to be verbose
    25. 25. Suspect Names Classes Manager Handler Methods Process “And”
    26. 26. Comments Use inline comments sparingly Do use docblocks though
    27. 27. TODOs Tend to rot and never get fixed If you use a TODO, add a ticket number
    28. 28. A well-named method that communicates intent is far more valuable than a comment
    29. 29. Methods You can (almost) always make a method smaller Pay attention to your execution path Check your CRAP index with phpmd or codesniffer You can (almost) always add more methods
    30. 30. Cognitive load Declare variables as close to when they will be used as possible If you can pass data from method to method directly, no need for a variable
    31. 31. Source order Declare methods in the order they’re called Public to private
    32. 32. Avoid “magic” values DRY Single source of truth Self-documenting
    33. 33. Which of these is easier to understand? json_last_error() == 5; json_last_error() == JSON_ERROR_UTF8; $length > 1024 $length > self::MAX_LENGTH $limit = 0 $limit = self::RATE_UNLIMITED
    34. 34. Not what I mean! unsigned three = 1; unsigned five = 5; unsigned seven = 7; https://github.com/torvalds/linux/blob/d158fc7f36a25e19791d 25a55da5623399a2644f/fs/ext4/resize.c#L698-700
    35. 35. “2 is a code smell” - Alex Miller
    36. 36. Dependency Injection Pass external dependencies into objects Constructor injection Setter injection Potential smell: too many dependencies Ask for things, don’t look for them
    37. 37. Object Calisthenics (briefly)
    38. 38. No more than one level of indentation per method
    39. 39. public function processData($data) { $newData = array(); $count = 1; foreach ($data as $row) { if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; }
    40. 40. … foreach ($data as $row) { // skip empty rows if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } …
    41. 41. public function processData($data) { $newData = array(); $count = 1; $data = $this->filterEmptyRows($data); foreach ($data as $row) { if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; } public function filterEmptyRows($rows) { return array_filter($rows); }
    42. 42. public function processData($data) { $newData = array(); $count = 1; $data = $this->filterEmptyRows($data); foreach ($data as $row) { $newData[] = $this->processRow($row, $count); $count++; } return $newData; } public function processRow($row, $count) { if ($count === 1) { return implode(',', array_keys($row)); } else { return implode(',', $row); } }
    43. 43. public function processData($data) { $newData = array(); $headersOnly = true; $data = $this->filterEmptyRows($data); foreach ($data as $row) { $newData[] = $this->processRow($row, $headersOnly); $headersOnly = false; } return $newData; } public function processRow($row, $headersOnly) { if ($headersOnly === true) { return implode(',', array_keys($row)); } else { return implode(',', $row); } }
    44. 44. Interlude… which is clearer? $this->setActive(true); $this->setActive(false); OR $this->activate(); $this->deactivate();
    45. 45. public function processRow($row, $headersOnly) { if ($headersOnly === true) { return $this->getHeaderRow($row); } else { return implode(',', $row); } } public function getHeaderRow($row) { return implode(',', array_keys($row)); }
    46. 46. public function processRow($row, $headersOnly) { if ($headersOnly === true) { return $this->getHeaderRow($row); } else { return $this->toCsv($row); } } public function toCsv($row) { return implode(',', $row); }
    47. 47. public function processData($data) { $newData = array(); $data = $this->filterEmptyRows($data); $firstRow= array_pop($data); $newData[] = $this->getHeaderRow($firstRow); foreach ($data as $row) { $newData[] = $this->toCsv($row); } return $newData; }
    48. 48. public function transformToCsv($data) { $data = $this->filterEmptyRows($data); $firstRow= array_pop($data); $csv = array(); $csv[] = $this->getHeaderRow($firstRow); foreach ($data as $row) { $csv[] = $this->toCsv($row); } return $csv; }
    49. 49. public function processData($data) { $newData = array(); $count = 1; foreach ($data as $row) { if (!$row) { continue; } if ($count === 1) { $newData[] = implode(',', array_keys($row)); } else { $newData[] = implode(',', $row); } $count++; } return $newData; }
    50. 50. Don’t use else
    51. 51. public function addThreeInts($first, $second, $third) { if (is_int($first)) { if (is_int($second)) { if (is_int($third)) { $sum = $first + $second + $third; } else { return null; } } else { return null; } } else { return null; } return $sum; }
    52. 52. public function addThreeInts($first, $second, $third) { if (!is_int($first)) { return null; } if (!is_int($second)) { return null; } if (!is_int($third)) { return null; } return $first + $second + $third; }
    53. 53. Command-Query Separation Complete separation between questions and commands “Asking a question shouldn’t change the answer”
    54. 54. public function getUser($id) { $user = $this->dataStore->fetchUser($id); if (!$user) { $user = new User(array($id)); } return $user; }
    55. 55. public function getUser($id) { return ($this->dataStore->fetchUser($id) ?: null; }
    56. 56. The final secret…
    57. 57. OOP is all about message passing and behaviors It’s not about inheritance It’s not about code reuse Favor composition over inheritance Treat objects like APIs
    58. 58. User Event Listener Mailer "Send Notification" w/User data So decouple! Such architecture So amaze Wow Much message
    59. 59. Summary Write small objects Write tiny methods Strive for good names Seek loose coupling Focus on message passing Treat objects like APIs Write tests (first) Refactor w/discipline Limit nesting/no else Use guard clauses Avoid magic (anything) Use CQS Avoid in-line comments Reduce cognitive load
    60. 60. Resources - Presentations The Clean Code Talks -- Inheritance, Polymorphism, & Testing Object Calisthenics
    61. 61. Resources – Books Code Complete: A Practical Handbook of Software Construction, Second Edition Growing Object-Oriented Software, Guided by Tests Refactoring: Improving the Design of Existing Code
    62. 62. Questions? https://joind.in/10562
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×