Of course, you should read all you can about SOLID, Design patterns, Patterns of Enterprise Application Architecture, etc. Once you have a basic understanding of these topics you still have to write that code though, and write it well too! What is good code? Are there some guidelines, or rules of thumb, which you can follow while making your everyday coding decisions?
In this talk Matthias will cover many of these coding guidelines, which aren’t usually covered by patterns or principles books. They should help you write better code and give you a richer vocabulary for reviewing other people’s code. Some of the subjects that we’ll discuss are: state, mutability, CQS, one-method objects, domain-first, API-driven, functional programming influences, object boundaries, (de)serialization, and more!
12. If not every string counts as an email address,
introduce a dedicated type: EmailAddress.
If not every integer counts as an age, introduce a
dedicated type: Age.
And so on!
13. Objects keep together what belongs
together
Latitude and Longitude
X and Y
Amount and Currency
Distance and Unit
...
18. final class Coordinates
{
private Latitude latitude;
private Longitude longitude;
public function halfwayTo(
Coordinates other
): Coordinates {
// ...
}
}
20. For all objects
Make sure they can't exist in an incoherent or
inconsistent state
21. At construction time
● Provide the right data (values, value objects)
● Provide the right services (collaborating objects)
22. // no setter injection
service = new Service();
service.setLogger(...);
// no setter party
object = new Object();
object.setFoo(...);
object.setBar(...);
24. What's the “right” data?
● Valid (correct types, correct number of things,
etc.)
● Meaningful (within an allowed range, pattern,
etc.)
25. What's the “right” data?
Only allow transitions that make sense
final class Order
{
public function cancel(...): void
{
if (wasShipped) {
throw new LogicException(...);
}
}
}
26. When implementing behavior: follow
this recipe
public function someMethod(int $value)
{
// check pre-conditions
Assertion::greaterThan($value, 0);
// fail early
if (...) {
throw new RuntimeException(...);
}
// happy path: at "0" indent
// check post-conditions
return ...;
}
28. public function commandMethod(...): void
{
/*
* Changes observable state of the system
*
* May have side effects:
* - network calls
* - filesystem changes
*
* Returns nothing.
* May throw an exception.
*/
}
29. public function queryMethod(...): [specific type]
{
/*
* Returns something, doesn't change
* anything.
*
* May throw an exception.
*/
}
40. Don't test constructors by calling
getters
Give the object a good reason to remember
something, and describe that reason in a test.
41. public function it_can_be_constructed(): void
{
money = new Money(1000, new Currency('EUR'));
assertEquals(
1000,
money.getAmount()
);
assertEquals(
'EUR',
money.getCurrency().asString()
);
}
Getters just
for testing!
42. public function it_can_be_converted(): void
{
money = new Money(1000, new Currency('EUR'));
rate = new ExchangeRate(
new Currency('EUR'),
new Currency('USD'),
1.23456
);
converted = money.convert(rate);
assertEquals(
new Money(
1235, // rounded to the second digit
new Currency('USD')
)
converted
);
}
No getters!
Nor testing them!
44. Changing the behavior of an object
Always aim to do it without touching the code of its class
(let it remain a black box!)
45. class Game
{
public function move(): void
{
steps = this.roll();
}
private function roll(): int
{
return randomInt(1, 6);
}
}
How to use a
hard-coded value
for testing?
46. class Game
{
public function move(): void
{
steps = this.roll();
}
protected function roll(): int
{
return randomInt(1, 6);
}
}
class GameWithFixedRoll extends Game
{
protected function roll(): int
{
return 6;
}
}
47. final class Game
{
private Die die;
public function __construct(Die die)
{
this.die = die;
}
public function move(): void
{
steps = this.die.roll();
}
}
interface Die
{
public function roll(): int;
}
Dependency injection!
Composition instead of
inheritance
Final classes!
48. final RandomDie implements Die
{
public function roll(): int
{
return randomInt(1, 6);
}
}
final FixedDie implements Die
{
public function __construct(value)
{
this.value = value;
}
public function roll(): int
{
return this.value;
}
}
Technically a test double
called “stub”
Behavior can be modified
without touching the code
50. Create better objects
● Introduce more types.
● Be more strict about them.
● Design objects that only accept valid data.
● Design objects that can only be used in valid
ways.
● Use composition instead of inheritance to
change an object's behavior.