is what you’ve coded what you mean? - dave liddament · 2020. 5. 28. · first let’s talk about...
TRANSCRIPT
Is what you’ve coded what you mean?
Dave Liddament (@daveliddament)
A confession….
@DaveLiddament
First let’s talk about bugs….
@DaveLiddament
Question 1: Who puts bugs in
their code?
@DaveLiddament
Question 2: When is the best
time to find a bug?
@DaveLiddament
Best time to find a bug?
@DaveLiddament
Best time to find a bug?
@DaveLiddament
Months into
operation
Best time to find a bug?
@DaveLiddament
Months into
operation
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Testing
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Testing
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Testing Writing code
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Testing Writing code
Best time to find a bug?
@DaveLiddament
Months into
operation
Feature is first used
Testing Writing code
Before writing code
@DaveLiddament
Why do bugs happen?
@DaveLiddament
What the code should do
Why do bugs happen?
@DaveLiddament
What the code should do
What the code actually does
Why do bugs happen?
@DaveLiddament
What the code should do
What the code actually does
What the developer thinks the code does
Why do bugs happen?
@DaveLiddament
What the code should do
What the code actually does
What the developer thinks the code does
Why do bugs happen?
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Why this talk?
@DaveLiddament
What the code should do What the
code actually does
What the developer thinks the code does
How we reduce cost of bugs
Pay attention!
@DaveLiddament
Dave Liddament @daveliddament
Lamp Bristol
15+ years software development (PHP, Java, Python, C)
Organise PHP-SW user group and Bristol PHP Training
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Obvious code
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Obvious code
Run time analysis
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Obvious code
Run time analysis
Static analysis
@DaveLiddament
Static analysis
@DaveLiddament
function process($user) { // some implementation }
$a = 1; process($a);
Is this code valid?
function process(User $user) { // some implementation }
$a = 1; process($a);
@DaveLiddament
Is this code valid?
function process(User $user) { // some implementation }
$a = 1; process($a);
@DaveLiddament
Is this code valid?
function process(User $user) { // some implementation }
$a = 1; process($a);
@DaveLiddament
Is this code valid?
function process(User $user) { // some implementation }
$a = 1; process($a);
@DaveLiddament
Is this code valid?
@DaveLiddament
@DaveLiddament
function process(User $user) { // some implementation }
$a = 1; process($a);
@DaveLiddament
Type hinting has helped
Months into
operation
Feature is first used
Testing Writing code
Before writing code
@DaveLiddament
More type hinting with PHP 7
function duplicateString ( string $value, int $times) :string
@DaveLiddament
More type hinting with PHP 7
function duplicateString ( string $value, int $times) :string
@DaveLiddament
More type hinting with PHP 7
function duplicateString ( string $value, int $times) :string
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
Is this code valid?
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(12); process($a);
@DaveLiddament
What the code actually does
What the developer thinks the code does
Win Win
@DaveLiddament
What the code actually does
What the developer thinks the code does
Win Win
@DaveLiddament
Language level validation
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(“dave”); process($a);
@DaveLiddament
Language level validation
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(“dave”); process($a);
@DaveLiddament
Language level validation
function getUser(int $id): User {…}
function process(User $user): void {…}
$a = getUser(“dave”); process($a);
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can cover language gapsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getAccountNumber(); }
@DaveLiddament
Static analysis can find errorsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getSomething(); }
@DaveLiddament
Static analysis can find errorsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getSomething(); }
@DaveLiddament
Static analysis can find errorsclass User { public function getAccountNumber() :string {…} }
/** * @return User[] */ function getUsers(): array { … }
$users = getUsers(); foreach($users as $user) { $accountNumber = $user->getSomething(); }
Static analysis helps developers
@DaveLiddament
Static analysis recap
@DaveLiddament
Static analysis recap• Analyse code without running it
@DaveLiddament
Static analysis recap• Analyse code without running it
• Prevent bugs even entering the code base
@DaveLiddament
Static analysis recap• Analyse code without running it
• Prevent bugs even entering the code base
• Type hinting and doc blocks comments help static analysis tools
•which in turn help developers
@DaveLiddament
Static analysis recap• Analyse code without running it
• Prevent bugs even entering the code base
• Type hinting and doc blocks comments help static analysis tools
•which in turn help developers
• Use an IDE that offers static analysis
@DaveLiddament
Run time checks
@DaveLiddament
Run time checks
• Tests
@DaveLiddament
Run time checks
• Tests
• Assertions
@DaveLiddament
Tests are assertions
@DaveLiddament
Tests are assertions
If I apply a discount code “SPEAKER”
@DaveLiddament
Tests are assertions
If I apply a discount code “SPEAKER”
My conference ticket is reduced to £90
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Benefits of testing
@DaveLiddament
What the code should do
What the code actually does
Benefits of testing
@DaveLiddament
What the code should do
What the code actually does
Benefits of testing
@DaveLiddament
What the code actually does
What the developer thinks the code does
Benefits of testing
What the code should do
@DaveLiddament
What the code actually does
What the developer thinks the code does
Benefits of testing
What the code should do
@DaveLiddament
Assertions in code
@DaveLiddament
Assertions in code
Statements that the developer believes should always be true
@DaveLiddament
Code that worries me…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; }
sendMessage($message);
@DaveLiddament
Code that worries me…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; }
sendMessage($message);
@DaveLiddament
Code that worries me…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; }
sendMessage($message);
@DaveLiddament
Code that worries me…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; }
sendMessage($message);
@DaveLiddament
Now I’m happier…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; } else { throw new Exception(“Invalid type”); } sendMessage($message);
@DaveLiddament
Now I’m happier…if ($type == 1) { $message = ‘hello’; } elseif ($type == 2) { $message = ‘goodbye’; } else { throw new Exception(“Invalid type”); } sendMessage($message);
public function setStatus(string $status){ $this->status = $status; }
@DaveLiddament
Can we improve this code
const REGISTERED = ‘registered’; const STARTED = ‘started’; const FINISHED = ‘finished’; const QUIT = ‘quit’;
public function setStatus(string $status){ $this->status = $status; }
@DaveLiddament
Improvement 1: Add constants
const REGISTERED = ‘registered’; const STARTED = ‘started’; const FINISHED = ‘finished’; const QUIT = ‘quit’;
public function setStatus(string $status){ $this->status = $status; }
@DaveLiddament
Improvement 1: Add constants
@DaveLiddament
Improvement 2: Add assertion… constants defined as before …
public function setStatus(string $status){ if (!in_array($status,[self::REGISTERED, self::STARTED, self::FINISHED]) { throw new Exception(“Invalid status”); } $this->status = $status; }
@DaveLiddament
Improvement 2: Add assertion… constants defined as before …
public function setStatus(string $status){ if (!in_array($status,[self::REGISTERED, self::STARTED, self::FINISHED]) { throw new Exception(“Invalid status”); } $this->status = $status; }
@DaveLiddament
Improvement 2: Add assertion… constants defined as before …
public function setStatus(string $status){ if (!in_array($status,[self::REGISTERED, self::STARTED, self::FINISHED]) { throw new Exception(“Invalid status”); } $this->status = $status; }
@DaveLiddament
Improving error messages
@DaveLiddament
Improving error messages
Invalid type
@DaveLiddament
Improving error messages
Invalid type
Invalid type [$type]
@DaveLiddament
Improving error messages
Invalid type
Invalid type [$type]
Invalid type [$type] for user [$userId]
@DaveLiddament
What the code actually does
What the developer thinks the code does
Wins from asserts
@DaveLiddament
What the code actually does
What the developer thinks the code does
Wins from asserts
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Benefits of assertions
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Benefits of assertions and a good test suite
@DaveLiddament
Obvious code
@DaveLiddament
Obvious code• Value Objects
@DaveLiddament
Obvious code• Value Objects
• Rename and Refactor
@DaveLiddament
Value Objects
@DaveLiddament
Can we improve this code?class MarketingCampaign {
… some methods …
public function addAddress(string $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“[email protected]”)
@DaveLiddament
Can we improve this code?class MarketingCampaign {
… some methods …
public function addAddress(string $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“[email protected]”)
@DaveLiddament
These are all strings…
fredblogs.comfred.blogs
6 Lower Park Row, Bristol
@DaveLiddament
These are all strings…
fredblogs.comfred.blogs
6 Lower Park Row, Bristol
@DaveLiddament
class MarketingCampaign {
.. some methods ..
public function addAddress(string $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“6 Lower Park Row, Bristol”)
This is wrong (and our IDE can’t spot mistake)
@DaveLiddament
class MarketingCampaign {
.. some methods ..
public function addAddress(string $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“6 Lower Park Row, Bristol”)
This is wrong (and our IDE can’t spot mistake)
@DaveLiddament
EmailAddress object instead of primitiveclass EmailAddress {
private $emailAddress; public function __construct(string $emailAddress) { $this->emailAddress = $emailAddress; }
public function getEmailAddress(): string { return $this->emailAddress; } }
@DaveLiddament
EmailAddress object instead of primitiveclass EmailAddress {
private $emailAddress; public function __construct(string $emailAddress) { $this->emailAddress = $emailAddress; }
public function getEmailAddress(): string { return $this->emailAddress; } }
@DaveLiddament
EmailAddress object instead of primitiveclass EmailAddress {
private $emailAddress; public function __construct(string $emailAddress) { $this->emailAddress = $emailAddress; }
public function getEmailAddress(): string { return $this->emailAddress; } }
@DaveLiddament
EmailAddress object instead of primitiveclass EmailAddress {
private $emailAddress; public function __construct(string $emailAddress) { $this->emailAddress = $emailAddress; }
public function getEmailAddress(): string { return $this->emailAddress; } }
@DaveLiddament
Using EmailAddressclass MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“[email protected]”) $campaign->addAddress($emailAddress)
@DaveLiddament
Using EmailAddressclass MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“[email protected]”) $campaign->addAddress($emailAddress)
@DaveLiddament
Using EmailAddressclass MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“[email protected]”) $campaign->addAddress($emailAddress)
@DaveLiddament
Using EmailAddressclass MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“[email protected]”) $campaign->addAddress($emailAddress)
@DaveLiddament
class MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“6 Lower Park Row, Bristol”)
This will fail (and your IDE will warn you)
@DaveLiddament
class MarketingCampaign {
.. some methods ..
public function addAddress(EmailAddress $address); }
$campaign = new MarketingCampaign(); $campaign->addAddress(“6 Lower Park Row, Bristol”)
This will fail (and your IDE will warn you)
@DaveLiddament
$emailAddress = new EmailAddress(“6 Lower Park Row”);
But this is wrong
@DaveLiddament
$emailAddress = new EmailAddress(“6 Lower Park Row”);
But this is wrong
@DaveLiddament
public function __construct(string $emailAddress) { if ( … check email address is valid… == false) { throw new Exception( “Invalid email address [$emailAddress]”); }
$this->emailAddress = $emailAddress; }
Add validation
@DaveLiddament
public function __construct(string $emailAddress) { if ( … check email address is valid… == false) { throw new Exception( “Invalid email address [$emailAddress]”); }
$this->emailAddress = $emailAddress; }
Add validation
We’re guaranteed that EmailAddress represents a correctly formatted email
address.
@DaveLiddament
Big win
@DaveLiddament
Are these email addresses the same?
@DaveLiddament
Store canonical form
public function __construct(string $emailAddress) {
… validate email address … $this->emailAddress = $this->getCanonical($emailAddress); }
@DaveLiddament
Store canonical form
public function __construct(string $emailAddress) {
… validate email address … $this->emailAddress = $this->getCanonical($emailAddress); }
@DaveLiddament
Postcodes formats
Normal: No spaces: Fixed width:
B1 1AB B11AB B1 1AB
@DaveLiddament
Add domain specific logic
public function getPostcode(): string {…}
public function getNoSpacesPostcode(): string {…}
public function getFixedWidthPostcode(): string {…}
@DaveLiddament
Are these positions equal?
A
B C
@DaveLiddament
Are these positions equal?
A
B C
@DaveLiddament
Add equals methodclass Point { const TOLERANCE = 10;
… Other methods …
public function equals(Point $other): bool { $distance = calculateDistance($this, $other); return $distance < self::TOLERANCE; } }
@DaveLiddament
Add equals methodclass Point { const TOLERANCE = 10;
… Other methods …
public function equals(Point $other): bool { $distance = calculateDistance($this, $other); return $distance < self::TOLERANCE; } }
@DaveLiddament
Be careful comparing objects… if ($point1 == $point2) { … some code … }
if ($point1->equals($point2)) { … some code … }
@DaveLiddament
Be careful comparing objects… if ($point1 == $point2) { … some code … }
if ($point1->equals($point2)) { … some code … }
@DaveLiddament
Be careful comparing objects… if ($point1 == $point2) { … some code … }
if ($point1->equals($point2)) { … some code … }
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Benefits of Value Objects
@DaveLiddament
What the code actually does
What the developer thinks the code does
Wins from Value Objects
@DaveLiddament
What the code actually does
What the developer thinks the code does
Wins from Value Objects
@DaveLiddament
Rename and Refactor
Kno
wle
ge
Time on project
@DaveLiddament
Renamingclass User { public function getName() {…} }
class Game { public function getName() {…} }
@DaveLiddament
Renamingclass User { public function getName() {…} }
class Game { public function getName() {…} }
@DaveLiddament
Renamingclass User { public function getName() {…} }
class Game { public function getQuest() {…} }
@DaveLiddament
Renamingclass User { public function getName() {…} }
class Game { public function getQuest() {…} }
@DaveLiddament
Renamingfunction getUser();
function getGame();
$user = getUser(); $game = getGame();
echo ‘Hello ‘ . $user->getName(); echo ‘You are playing ‘ . $game->getName();
@DaveLiddament
Renamingfunction getUser();
function getGame();
$user = getUser(); $game = getGame();
echo ‘Hello ‘ . $user->getName(); echo ‘You are playing ‘ . $game->getName();
@DaveLiddament
Renamingfunction getUser(): User;
function getGame(): Game;
$user = getUser(); $game = getGame();
echo ‘Hello ‘ . $user->getName(); echo ‘You are playing ‘ . $game->getQuest();
@DaveLiddament
Renamingfunction getUser(): User;
function getGame(): Game;
$user = getUser(); $game = getGame();
echo ‘Hello ‘ . $user->getName(); echo ‘You are playing ‘ . $game->getQuest();
@DaveLiddament
Renamingfunction getUser(): User;
function getGame(): Game;
$user = getUser(); $game = getGame();
echo ‘Hello ‘ . $user->getName(); echo ‘You are playing ‘ . $game->getQuest();
@DaveLiddament
Win-Win: Rename and refactor
Months into
operation
Feature is first used
Testing Writing code
Before writing code
Summary
@DaveLiddament
What the code should do What the
code actually does
What the developer thinks the code does
Summary
@DaveLiddament
Summary
@DaveLiddament
Summary
• Type hint everything you can
@DaveLiddament
Summary
• Type hint everything you can• Use docblock for language gaps
@DaveLiddament
Summary
• Type hint everything you can• Use docblock for language gaps• Write tests
@DaveLiddament
Summary
• Type hint everything you can• Use docblock for language gaps• Write tests• Add assertions
@DaveLiddament
Summary
• Type hint everything you can• Use docblock for language gaps• Write tests• Add assertions• Use Value Objects
@DaveLiddament
Summary
• Type hint everything you can• Use docblock for language gaps• Write tests• Add assertions• Use Value Objects• Rename and refactor
@DaveLiddament
Summary
• Use a modern IDE
@DaveLiddament
Questions