Skip to content

Latest commit

 

History

History
147 lines (131 loc) · 7.82 KB

2017-phpday17-extremely-defensive-php.md

File metadata and controls

147 lines (131 loc) · 7.82 KB

Extremely defensive PHP

Marco Pivetta - GrUSP phpDay 2017


Available resources

🏷️ Tags: talk, 2017, phpday17, php, poka-yoke, yagni, immutability, strict, programming, object-design


Introduction

  • Anything you skip today, you'll have to fix tomorrow
  • This talk is about aggressive practices that makes completely sense if you have a project that has to live for a long time
    • Is not for every kind of project, not for the smallest
  • Defensive driving: the idea that shit happens, so is pretty much assuming that something will go wrong and you have no control over it
    • So you have to keep an eye over everything, in a very proactive way
  • Defensive coding is like defensing driving
    • Not just what you wrote it you have to assume that is going to work always or is going to be used in the way it's supposed to
    • It's about being cautious about everyone else's code, and that includes your own code
  • Concept of Poka-yoke
    • Japanese term that means "mistake-proofing" or "inadvertent error prevention". Avoiding mistakes
    • Example of good applied poka-yoke: the RJ45 plug
      • You can plug it on in only one way. You know when it clicks the contact is working. So, this design is good design
    • Example of badone: the USB connector
      • This is a device of doom. Comes from different dimensions, so you plug in one side and it doesn't work, turning it upside down doesn't work again, etc. you commonly need several tries to make it work... so, it's maybe not such a good design. Specially when it¡s behind the computer and you have no idea what's going on
  • Code is not reusable
    • What is reusable are the interfaces. The definitions, the protocols
    • How to make things talk to each other
    • But, what is inside every block most of the times is not important
    • The important is the abstraction, so defining abstractions is what is reusable (interfaces in classes, or function signatures)

Practices

  • Assuming SOLID code
  • Also, assuming some object calisthenics
    • It's not something you should implement, it's something you should strive for
  • Make state immutable
    • Make it and you'll reduce the amount of values (state) changing, also reduce the complexity around state
    • With this, also you will get rid of lot of bugs
    • It comes at the cost of performance, because the PHP language is not really designed to continuously garbage collect everything that goes on
    • Anyway, one call to the database is probably worth millions of calls to immutable objects
    • So, do profile and consider immutability as a problem only when you have operations that are bound to the amount of data you're processing
    • Particularly important for Value Objects
  • No setters
    • They don't really mean anything
    • In OOP, objects talk with each other. They tell objects what to do, or what they want
    • But, they shouldn't talk to other objects about what to be (set)
  • The constructor is the only injection point
    • So, all the state injection/making is done in the constructor
    • Particularly important for Service Layers
  • Don't have uninitialized properties
    • For instance __construct(LoggerInterface $logger = null) and a setLogger. Don't do it
    • The concept of optional dependencies is generally a bad idea
    • The problem is that "optional" dependencies does not exist, because the dependency is there
    • If you remove this concept, you also remove lot of conditional code that needs to check for dependency initialization
    • Following the example. If we don't want to log, then we can pass in a fake logger (but an instance of the Logger abstraction / interface)
    • This is what you also do on UNIX. Every process has a stderr and a stdout. And if you don't want any output, just redirect everything to /dev/null
  • Avoid unnecessary public methods
    • This is because the problem with public API is when you wrote it, you have to maintain it
    • "A public method is like a child: once you've written it, you are going to maintain it for the rest of its life" (Stefan Priebsch)
    • Specially if you write a public library
  • Avoid logic switch parameters
  • All state should be encapsulated
  • Any external access includes subclasses
    • Don't use protected, use private
  • Make the state private. Private properties by default
  • A public method is a transaction
    • If you have several public methods that are strong related, that needs should be called (and succeeded) at a time, we should make them a single callable method
    • If there is a failure, we should to design against mutations of state in this failure
  • Do not assume method idempotence
    $userId = $controller->request()->get('userId');
    $userRoles = $controller->request()->get('userRoles');
    
    • Subsequent method calls are assuming that the request will be the same. Avoid doing it and call it once
    $request = $controller->request();
    $userId = $request->get('userId');
    $userRoles = $request()->get('userRoles');
    
  • Assert
    • Anything that comes outside from your domain, is potentially invalid data
    • Enforce strict invariant on anything that comes from the outside
  • Don't accept mixed parameter types
    • Extract its meaning, and make its own concept (a value object
    • Give it a name, it's a concept, has a type
  • Value Objects FTW
    • Simplify things a lot
    • Once you create then, it's no longer an int, it's the "user id", it's no longer a string, it's a "email address", etc.
    • You have to define a type
  • Don't accept return mixed types
    • As soon as you define an API that return mixed, you're showing the middle finger to the users of your API
    • You are telling them like I have no idea, or I don't care about what I return you. Just figure out yourself
  • Avoid and remove fluent interfaces
    • Many reasons to avoid them. One of them:
    // way 1
    $thing
      ->doFoo()
      ->doBar();
    
    // way 2
    $thing = $thing->doFoo();
    $thing = $thing->doBar();
    
    // way 3
    $thing->doFoo();
    $thing->doBar();
    
    • Why having three ways to do the same, if I can have only one (way 3)
    • This is not poke-yoke
  • Make classes final by default
    • Class are not final by default in PHP language, but it should
    • An extension is a different use-case
    • A trick to find out whether you should make a class extensive or not, is removing all properties and body implementations of the methods and imagine that that class is an interface
      • Does it make sense to extend that interface? That adds some semantic meaning?
      • Is it really some kind of subtype? When you extend, you have to think in types, not in behaviour
  • Avoid and remove Traits
    • Let's them die
    • DRY misconception. This principle is not about code duplication, it's about concept duplication
  • Ensure deep cloning
    • It should be applied any time you have something that you can clone (objects)
    • Cloning requires encapsulated state understanding. What its lifecycle is, what the lifecycle of its internals are
  • Disable clone in scenarios you just don't need it
  • Same for serialization. Disable in scenarios you just don't need it
    • And if you really want to serialize() something make sure it implements the Serializable interface
    • So you designed for it to be serialized
  • Unit test all scenarios

Recap

  • All of these are suggestions
  • Don't trust anybody's code (even your own). You wrote it, it's legacy
  • Try designing up front, try designing the interface before the code
  • Reduce features to the bare minimum. Follow the YAGNI principle: you don't need it, you don't write it
  • Go for strict imnariants, in and out

Further reading