Confessions of a code poet
Stephan Hochdörfer // 2017-12-04
About me
Stephan Hochdörfer
Head of Technology , bitExpert AG (Mannheim, Germany)
S.Hochdoerfer@bitExpert.de
@shochdoerfer
#PHP, #DevOps, #Automation
#phpugffm, #phpugmrn, #unKonf
« Just because I know a language does not make me Shakespeare! #coding » - @cj_harris5
function build_rand_array ( $array_size )
{
$array = array ();
do {
$random_number = rand ();
if (! in_array ( $random_number , $array )) {
$array [] = $random_number ;
}
} while ( count ( $array ) < $array_size );
return $array ;
}
Is coding art or craft?
Is coding art or craft?
« good programmers don't need to comment their code, art is self explanatory » - @failnaut
Do I agree with this quote? Yes and no. First of all, sure you should create code that speaks for itself
(e.g. clear var names) and comments should not repeat 1:1 what the codes. But we still need documentation
how things relate to each other.
No comments needed?
function build_rand_array ( int $array_size ): array
{
$array = array ();
do {
$random_number = rand ();
if (! in_array ( $random_number , $array )) {
$array [] = $random_number ;
}
} while ( count ( $array ) < $array_size );
return $array ;
}
While it seems to be clear for the function argument what one needs to pass, what about the return type?
Sure, you will get an array back, but how is it structured? Are there special semantics I should be aware of?
No comments needed?
/**
* Returns a one-dimensional, index-based array containing
* random numbers.
*
* @param int $array_size
* @return int[]
*/
function build_rand_array ( int $array_size ): array
{
$array = array ();
do {
$random_number = rand ();
if (! in_array ( $random_number , $array )) {
$array [] = $random_number ;
}
} while ( count ( $array ) < $array_size );
return $array ;
}
Looks better to me. Better but not perfect, maybe we should rename the function parameter and add it to the
docblock description as well. The gist of it: Even though be have parameter and return type hints these days,
do not go beserk and remove all comments because those are not needed any more. I'd also leave them in for
consistency reasons!
Code formatting style
Code formatting style
function build_rand_array ( $iArraySize )
{
$aRandomArray = array ();
do
{
$iNumber = rand ();
if (! in_array ( $iNumber , $aRandomArray ))
{
$aRandomArray [] = $iNumber ;
}
}
while ( count ( $aRandomArray ) < $iArraySize );
return $aRandomArray ;
}
This used to be the coding standard I used when I started doing PHP and was the one we used when we started
to run bitExpert. The original idea was good: Pick one company-wide coding standard and use them everywhere.
But we failed.
Code formatting style
function build_rand_array ( $array_size )
{
$array = array ();
do {
$random_number = rand ();
if (! in_array ( $random_number , $array )) {
$array [] = $random_number ;
}
} while ( count ( $array ) < $array_size );
return $array ;
}
Few years back I decided I could not stand seeing no consistency in our projects any more. I went to the
team and explained what I would like to do. I got their ok and we moved to using the standards for each
programming language we use. Feels much better now ;)
Code formatting style
« Do not invent your own coding standard. Instead, focus on the important parts! » - Me
What is "the important parts"? Deliver business value. Easy as that.
Code organization
./ project
|- bin
|- config
|- public
|- src
|--- SomeApplication
|----- Action
|------- Module1
|------- Module2
|----- Dao
|------- Module1
|------- Module2
|----- Service
|------- Module1
|------- Module2
|----- Views
|------- Module1
|------- Module2
That worked "ok" for smaller apps, the larger the projects grew, the worse it got.
Code organization
./ project
|- bin
|- config
|- public
|- src
|--- SomeApplication
|----- Module1
|------- Action
|------- Dao
|------- Service
|------- Views
|----- Module2
|------- Action
|------- Dao
|------- Service
|------- Views
Another popular approach, using the modules/plugins on the first level and then split the "jobs".
Code organization
« Don’t organize code according to its job title (model, view, utility); sort it according to its purpose (feature). » - @jessitron
Code organization
« Keeping things together just because they are of same type, instead of grouping them by domains, should be a number one anti-pattern. Yet still it's the default of many frameworks and people keep using forever. Please, stop!
» - @bartsokol
Unfortunately most frameworks do it wrong. Framework tutorials should better use real domain examples than
just showing their functionality.
Code organization
./ project
|- bin
|- config
|- public
|- src
|--- Attendee
|--- Cli
|--- Day
|--- Http
|--- OAuth2
|--- Sync
|--- TalkProposal
|--- Talk
|--- Track
This is a much better approach and can easily be done with PSR-4. It is ok to use a lot of top-level modules.
Let's give this one a try. Who of you has an idea what the domain of this tool could potentially be?
Code organization
./ project
|- src
|--- TalkProposal
|----- Events
|----- ListTalkProposals.php
|----- ProposeTalk.php
|----- RemoveTalkProposal.php
|----- TalkProposal.php
|----- TalkProposalAbstract.php
|----- TalkProposalFinder.php
|----- TalkProposalId.php
|----- TalkProposalReadModel.php
|----- TalkProposalRepository.php
|----- TalkProposalTitle.php
|----- UpdateTalkProposal.php
Code organization
« Organize code according to its purpose » - Me
Use all the sophisticated features of your IDE to navigate in your code. If you don't use an IDE, install
one right after this talk.
What about my util package?
Code organization
« Core, common or util are no valid package names! » - Me
What purpose has the core package? Well it offers the core functionality of the app. What is that?
Well login, user management, ...
All of those names clearly show that you have no idea what you are doing. Which brings me to...
Naming things
« If you don't know what a thing should be called, you cannot know what it is. If you don't know what it is, you cannot sit down and write the code. » - Sam Gardiner
Naming things
« LazyLoadingValueHolderFactory » - ProxyManager
Naming things
« Germans are uniquely qualified to work on #SpringFramework code because even after AbstractSingletonProxyFactoryBean we still have half a breath left » - Oliver Gierke
Naming things
« Hauptversionsnummererhöhungsangst »
(fear of increasing the major version in semver)
The beauty of the German language: We can simply take any sentence and turn that into a noun by concatenating
the words.
Naming things
« Use names that clearly express their intent. » - Me
Naming things gone wrong!
« Microservices for the Frontend »
I do understand the "Microservices" are popular but you should understand the basic idea behind it before
using the term in a different context.
Naming things wrong!
« the container » - almost every DI library in PHP (except Disco)
In no other programming language is the DI container so important as it is in PHP. We even have a common
name for it "the container". Why? If the DI container is more important than the domain of your application
you are doing it wrong. DI is the most boring part of all. Trust me.
DI Config gone wrong
<?xml version ="1.0" encoding ="UTF-8" ?>
<container xmlns ="http://symfony.com/schema/dic/services"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation ="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd" >
<services>
<service id ="dependency" class ="\My\Dependency" >
</service>
<service id ="manager" class ="\My\Manager" >
<call method ="setDependency" >
<argument type ="service" id ="dependency" />
</call>
</service>
</services>
</container>
If you have followed my work in the past you might remember that I was always a big fan of XML as DI config language.
For a ong time I did not understand why the Spring Framework moved away from XML config to code, that is why we spent
the last 10yrs focusing on XML. But why code? 2 reasons: Refactoring support + "Language correctness"
https://twitter.com/rdohms/status/811911988586627072
DI Config gone wrong
« Is there a way to run a test that builds the Symfony DIC and forces instantiation of all objects? to weed out any errors? » - @rdohms
DI Config done right!
« [...] a PSR-11 compatible, annotation based dependency injection container. » - Disco
DI Config done right!
< ?php
use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;
/** @Configuration */
class Config
{
/** @Bean */
protected function database () : Database
{
return new Database ( 'mysql://user:secret @localhost /mydb' );
}
/** @Bean */
public function productService () : ProductService
{
return new ProductService ( $this -> database ());
}
/** @Bean */
public function productController () : ProductController
{
return new ProductController ( $this -> productService ());
}
}
Using a class for configuration. You can use all your IDE's features to detect "problems" in the configuration.
Bonus point for using constructor injection
Too many dependencies?
< ?php
class ProductController extends Controller
{
public function __construct (
ProductService $productService ,
ProductImageService $productImageService ,
PricingService $pricingService ,
CustomerService $customerService ,
BasketService $basketService ,
// [...]
) {
// [...]
}
}
It is actually pretty hard to come up with sample code that sucks :)
ContainerAware is a lie!
< ?php
interface ContainerAware
{
public function setContainer ( ContainerInterface $container );
}
< ?php
class ProductController extends Controller
implements ContainerAware
{
protected $productService ;
public function setContainer ( ContainerInterface $container )
{
$this -> productService = $container -> get ( 'productService' );
}
}
There is so much wrong with in these few lines of code. Never in inject the DI container
into your application classes.Never use the DI container as a registry: we depend on a string "productService".
We do not what type it really is. We simply trust the container to deliver what we need. We
hide the dependencies we really need from the outer world, which makes it hard to test.
Is it really a dependency?
< ?php
class ProductController extends Controller
{
protected $productService ;
public function __construct ( ContainerInterface $container )
{
$this -> productService = $container -> get ( 'productService' );
$this -> productImgService = $container -> get ( 'productImgService' );
$this -> pricingService = $container -> get ( 'pricingService' );
$this -> customerService = $container -> get ( 'customerService' );
$this -> basketService = $container -> get ( 'basketService' );
}
}
Is the container really a dependency or do you just inject it so that you have no need to deal with some many
dependencies yourself? Probably the latter.
What is a dependency?
Sometimes coding feels like being trapped in an forrest, you cannot see the sky. All you see is trees and
all those trees look like the same, even though in fact there are different types of trees arround. Similar
to coding. Not every object is the same.
Is a logger a dependency?
Is a logger a dependency?
< ?php
class ProductController extends Controller
{
protected $logger ;
public function __construct ()
{
$this -> logger = new Logger ( '/tmp/application.log' );
// [...]
}
}
{
"require" : {
"psr/log" : "^1.0" ,
"monolog/monolog" : "^1.21"
}
}
Harcoding the logger in every class does not help, especially when you have to pass the log file path
over and over again. Library problem: Does not help when you depend on a PSR-3 but use a concrete PSR-3
implementation.
LoggerAwareInterface?
< ?php
namespace Psr\Log;
/**
* Describes a logger-aware instance.
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object.
*
* @param LoggerInterface $logger
* @return null
*/
public function setLogger ( LoggerInterface $logger );
}
PSR-3 offer us this little nifty interface (plus a trait) to implement.
Constructor injection maybe?
< ?php
class ProductController extends Controller
{
protected $logger ;
public function __construct ( \Psr\Log\LoggerInterface $logger )
{
$this -> logger = $logger ;
// [...]
}
}
Simple Logging Facade
« To achieve true interoperability, your own code should not depend on a specific library implementing the PSR-3. » - slf4psrlog
bitexpert/slf4psrlog
$> composer.phar require bitexpert/ slf4psrlog
< ?php
class ProductController extends Controller
{
protected $logger ;
public function __construct ()
{
$this -> logger = \b itExpert\Slf4PsrLog\LoggerFactory:: getLogger (
__CLASS__
);
// [...]
}
}
If not custom logger instance was provided (e.g. no set-up was run) the LoggerFactory will simply
return a NullLogger instance so that your code will not break!
The glue code...
< ?php
\bitExpert\Slf4PsrLog\LoggerFactory:: registerFactoryCallback (
function ( $channel ) {
if (! \Monolog\Registry:: hasLogger ( $channel )) {
\Monolog\Registry:: addLogger (
new \Monolog\Logger ( $channel )
);
}
return \Monolog\Registry:: getInstance ( $channel );
}
);
In your application code you need to configure the LoggerFactory. This is not needed in our library! You
can however configure the logger when executing your unit tests and have a concrete logger implementation
in your require-dev section of composer.json.
Simple Logging Facade
« A logger is part of your infrastructure and not a dependency of your class. » - Me
What about Interfaces?
What about Interfaces?
« Interfaces are not an overhead, they help to structure your code. » - Me
Interfaces done wrong!
< ?php
class ProductRepository
{
}
< ?php
class ProductRepository implements ProductRepositoryInterface
{
}
< ?php
class ProductController extends Controller
{
public function __construct ( ProductRepositoryInterface $repository )
{
// [...]
}
}
Interfaces done right!
< ?php
interface ProductRepository
{
}
< ?php
class PostgreSqlProductRepository implements ProductRepository
{
}
< ?php
class XmlFileStorageProductRepository implements ProductRepository
{
}
Start first with the interface. The interface introduces a new type to your domain and it sets up a new
contract for your code to rely on.
Implementation names should answer the question: What does the implementation really do?
Wrong Interface placement
./ project
|- bin
|- config
|- public
|- src
|--- ProductCatalog
|----- Action
|----- Repository
|------- ProductRepository.php
|------- PostgreSqlProductRepository.php
|------- XmlFileStorageProductRepository.php
Another thing that can be seen often these days: Placing the implementations next to the interface (or in
case of Composer in the same package). While this simplifies things for the package author, the intent isn't
communicated clearly.
Right Interface placement
./ project
|- bin
|- config
|- public
|- src
|--- ProductCatalog
|----- ListProducts.php
|----- ProductDetails.php
|----- ProductRepository.php
|--- PostgreSqlBackend
|----- PostgreSqlProductRepository.php
|--- XmlFileBackend
|----- XmlFileStorageProductRepository.php
The whole idea is that the action package (namespace) can live on its own without any outside dependencies
(ideally).
And finally...
« Use `final` in your code. Wherever you can. » - Me
You should avoid deep class nesting levels at all costs. The final keyword helps you with that.
Finalize your models
< ?php
final class Customer extends AggregateRoot
{
/** @var CustomerId */
private $customerId ;
/** @var string */
private $firstname ;
/** @var string */
private $lastname ;
/** @var EmailAddress */
private $emailAddress ;
/** @var PasswordHash */
private $password ;
// [...]
}
Finalize your events
< ?php
final class PasswordWasChanged extends AggregateChanged
{
/** @var CustomerId */
private $customerId ;
/** @var PasswordHash */
private $passwordHash ;
// [...]
}
Finalize your Value objects
< ?php
final class EmailAddress implements ValueObject
{
/** @var string */
private $email ;
// [...]
}
Finalize your repositories
< ?php
final class CustomerRepository extends AggregateRepository
{
/**
* Persists given $customer in the repository.
*
* @param Customer $customer
*/
public function save ( Customer $customer ): void
{
$this -> saveAggregateRoot ( $customer );
}
// [...]
}
Back to the basics
« I have a problem with introducing junior developers to web dev using massive tooling setup where the whole web platform is abstracted away.
» - @Lady_Ada_King
Focus on good code
« More good code has been written in languages denounced as bad than in languages proclaimed wonderful - much more. » - Bjarne Stroustrup
Thank you! Questions?
Stephan Hochdörfer
Head of Technology , bitExpert AG (Mannheim, Germany)
S.Hochdoerfer@bitExpert.de
@shochdoerfer
#PHP, #DevOps, #Automation
#phpugffm, #phpugmrn, #unKonf