is what you’ve coded what you mean? - dave liddament · 2020. 5. 28. · first let’s talk about...

Post on 11-Sep-2020

1 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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(“dave@phpsw.uk”)

@DaveLiddament

Can we improve this code?class MarketingCampaign {

… some methods …

public function addAddress(string $address); }

$campaign = new MarketingCampaign(); $campaign->addAddress(“dave@phpsw.uk”)

@DaveLiddament

These are all strings…

dave@phpsw.uk

fred@blogs.com

fredblogs.comfred.blogs

6 Lower Park Row, Bristol

@DaveLiddament

These are all strings…

dave@phpsw.uk

fred@blogs.com

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(“dave@phpsw.uk”) $campaign->addAddress($emailAddress)

@DaveLiddament

Using EmailAddressclass MarketingCampaign {

.. some methods ..

public function addAddress(EmailAddress $address); }

$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“dave@phpsw.uk”) $campaign->addAddress($emailAddress)

@DaveLiddament

Using EmailAddressclass MarketingCampaign {

.. some methods ..

public function addAddress(EmailAddress $address); }

$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“dave@phpsw.uk”) $campaign->addAddress($emailAddress)

@DaveLiddament

Using EmailAddressclass MarketingCampaign {

.. some methods ..

public function addAddress(EmailAddress $address); }

$campaign = new MarketingCampaign(); $emailAddress = new EmailAddress(“dave@phpsw.uk”) $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?

dave@phpsw.uk

dave@PHPSW.uk

DAVE@phpsw.UK

DAVE@phpsw.uk

@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

@DaveLiddament

Advice for improving

https://joind.in/talk/8de76

top related