Prato NV
Wouter Groeneveld & Luk Weyens
30/09/2019
GSM Uit aub
Verstand Aan aub
Vragen stellen toegelaten!
Clean code is code that is easy to understand and easy to change.
function process($obj, $continue = FALSE) {
// echo "starting the process";
if($obj->generate() == NULL) {
return -1;
}
$result = $obj->generate();
if($continue) {
return $result->generate();
}
return $result;
}
function getBookByISBN($isbn) {
$book = array_search($isbn, $this->books);
if($book == FALSE) {
throw new BookNotFoundException("book was not found!");
}
return $book;
}
Code gets read a lot (at least whenever someone is writing more code), so any school of clean code should emphasize readability. Cleaning up a little wherever you go is required to keep code clean.
$result = $processor->process();
VS
$order = $library->checkOut();
/**
*
* Prolong the book
*
* @param Book $i The book to prolong
* @param DateTime $j The date to prolong to
* @param Person $k The person to prolong the book to
* @return nothing if OK
*
*/
function prolong($i, $j, $k) {
//Check if this book has been checked out by the same person
if($i->person != $k)
//BUSTED!
return "This is not a book you checked out."
else
//Everything OK, prolong the book
$book->checkOutDate = $date;
}
function prolong($book, $date, $person) {
if(!$book->isCheckedOutBy($person))
return "This is not a book you checked out."
else
$book->checkOutDate = $date;
}
class Book {
function prolong($date, $person) {
// does only one thing: prolonging the book
}
}
function checkoutBook($title, $isbn, $person, $enoughCredit = FALSE) {
// ...
}
VS
class Person {
function checkoutBook($book) {
// ...
}
}
class Book {
function prolong($date, $person) {
//prolong implementatie
if($this->isOverDue()){
$person->credit -= 10;
}
}
}
class Book {
function prolong($date, $person) {
//prolong implementatie
}
}
if($book.isOverdue($date)){
$person->applyFine();
}
$book->prolong($date, $person);
public class BookRepository {
public $db;
public $bookCache = [];
}
$bookRepositoryInstance->db->query('where isOverdue = TRUE');
public class BookRepository {
private $db;
public function getOverdueBooks() {
return $db->query('where isOverdue = TRUE');
}
}
$bookRepositoryInstance->getOverdueBooks();
$checkoutDate = NULL;
function isOverdue($book) {
global $checkoutDate;
// some domain logic here
$checkoutDate = new Date();
}
$book->setCheckoutDate($checkoutDate);
Zie ook static e.a.
$bookid = $_POST['bookid'];
function prolong($book, $date, $person) {
$book->checkOutDate = $date;
$person->bookList->push($book);
}
}
prolong($db->getby($bookid), $_POST['date'], $person);
echo "thank you it has been prolonged"
class Book {
function prolong($date, $person) {
$person->addToBookList($this);
}
}
$book = new Book();
$book->prolong($_POST['date'], $person);
$view->renderThankYouPage();
class BookTest extends TestCase {
public void testProlongAddsToPersonBookList() {
// ...
}
}
function getAisle($book) {
switch($book->type) {
case 'nonficition': return 400;
case 'fiction': return 234;
}
}
getAisle({ type: 'nonfiction' });
abstract class Book {
abstract public function getAisle();
}
class FictionBook extends Book {
function getAisle() {
return 234;
}
}
class NonFictionBook extends Book {
function getAisle() {
return 400;
}
}
new NonFictionBook()->getAisle();
$repository->getBookById(124)->getAuthor()->getAllBooks()->filterByName('train');
function getBookById($id) {
if($id == NULL) return -1;
return $db->getById($id);
}
function getBookById($id) {
if($id == NULL) throw new InvalidIdException("id cannot be null!");
return $db->getById($id);
}
Structuur veranderen, zonder inhoud te wijzigen!
class Dierentuin {
public function bezoek() {
// return [] of dieren
}
public function ontvangDier($dier) {
// ??
}
}
class Dierentuin {
public function voeder($voedsel) {
return TRUE; // ??
}
}
public function voeder($voedsel) {
// wat nu??
}
class Dier {
public function isAllergischAan($voedsel) {
return TRUE; // ??
}
}
You cannot write perfect software. Therefore, do not waste energy trying;
be pragmatic.
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
public function testFormula_evaluatesSimpleCalculation() {
$formula = "3+5";
$this->assertEquals(8, eval('return ' . $formula . ';'));
}
Gebruik nooit eval() in productiecode!Programmers are really authors, and your target audience is not the computer it is other programmers (and yourself).
function render() {
renderRest();
}
function renderRest() {
document.getElementById('#ak').innerHTML = 'Hello PXL!';
}
VS
function renderHelloPXLPage() {
document.getElementById('#content').innerHTML = 'Hello PXL!';
}
function getBodyElements() {
return document.querySelector('.body').forEach(function(i) {
bodies++; // ???
});
}
VS
function getBodyElements() {
return documents.querySelector('.body');
}
function getAmountOfBodyElements() {
return getBodyElements().length;
}
function prolongBook() {
if(arguments[1]) {
var overdue = true;
}
if(arguments[0]) {
arguments[0].prolong(overdue);
}
}
VS
function prolongBook(book, isOverdue) {
// ...
}
function renderContentCallback() {
if(!this.rendered) {
reRenderContent();
}
this.rendered = true;
}
Zelfde principe in elke taal...
var obj = {
isRendered: false,
render: function() {
if(!this.isRendered) {
refresh();
}
this.isRendered = true;
}
};
Is isRendered voldoende afgeschermd?
obj = (function() {
var isRendered = false;
var render = function() {
// ...
}
return {
render: render
};
})();
(function() {
// niemand kan aan mij! FUNCTION LEVEL SCOPE
})();
Expose enkel wat jij wil als publieke interface:
myAPI = (function() {
return {
// enkel DIT is aanspreekbaar
};
})();
function main() {
if (TRUE) {
$i = 3;
}
echo $i;
}
main(); // wat doet dit?
Waarom?
var main = function () {
if (true) {
var i = 3;
}
console.log(i);
}
main(); // wat doet dit?
wordt door interpreter dit:
var main = function () {
var i = undefined;
if (true) {
i = 3;
}
console.log(i);
}
void main() {
if (true) {
int i = 3;
}
printf("%d", i); // wat doet dit?
}
Waarom? Statisch getypeerd!
function calculateBookPrice() {
discount = 10; // oops??
return this.basePrice + calculateOverduePrice() - discount;
}
Wat is de waarde van window.discount?
VS
Pear PHP standaarden:
var bookid = document.querySelector('#bookid').innerHTML;
var prolong = function(book, date, person) {
book.checkOutDate = date;
person.bookList.push(book);
}
};
doAjaxCall('getBookById.json', bookid, function(book) {
prolong(book, someDate, person);
});
document.querySelector('#messages').innerHTML = "thank you";
Wat gaat er mis?
// ------ domain/book.js ------
var Book = {
prolong: function(date, person) {
person.addToBookList(this);
}
};
// ------ controller/bookid.js ------
var bookid = view.getBookId();
doAjaxCall('getBookById.json', bookid, function(book) {
book.prolong(someDate, person);
});
view.renderThankYouPage();
Wat gaat er mis?
doAjaxCall('getBookById.json', bookid, function(book) {
book.prolong(someDate, person);
view.renderThankYouPage();
});
describe("Book tests", function() {
it("should add book to person list", function() {
var book = Object.create(Book);
book.prolong(someDate, person);
expect(person.getBookList()).toContain(book);
});
});
Héél belangrijk in goed onderhoudbare JS code:
Moeilijk testbare code => beter eerst testen geschreven??
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
Beschrijft gedrag
class Dierentuin {
constructor() {}
bezoek() {
// return [] of dieren
}
ontvangDier(dier) {
// ??
}
}
class Dierentuin {
voeder(voedsel) {
return true; // ??
}
}
public function voeder($voedsel) {
// wat nu??
}
class Dier {
public function isAllergischAan($voedsel) {
return TRUE; // ??
}
}
Van PHP naar JS met ES6
JS VS PHP:
class Dier {
constructor(arg) { } // __construct()
eet(voedsel) { // public function eet($voedsel)
this.boer(); // $this->boer();
}
boer() { // public function boer()
console.log('burp'); // echo "burp";
}
}
new Dier().eet(); // new Dier()->eet();
Herwerk je code!Eerst leren kruipen, dan lopen!
If a feature is sometimes useful and sometimes dangerous and if there is a better option then always use the better option.Code Quality tools demo @ JSLint voor:
"use strict";
function bla() {
mwaha = "hi";
if(mwaha == "hi") {
console.log("hi there too")
}
}
var Book = {
prolong: function(person) {
var date = Date.now();
this.dbHandle.prolong(date, person.id);
}
};
Ik heb dbHandle.prolong() apart getest, wat nu?
Injecteer een andere dbHandle: een "mock".
var book = Object.create(Book);
book.dbHandle = {
prolong: function(date, personId) {
// Captured arguments! Assert stuff here
}
};
Laat Jasmine "spies" aanmaken
https://jasmine.github.io/api/2.6/global.html#spyOn
it("should call prolong from repository", () => {
let personId;
const dbHandle = spyOn('dbHandle', 'prolong').and.callFake((theDate, thePersonId) => {
personId = thePersonId;
});
book.dbHandle = dbHandle;
book.prolong(person);
expect(personId).toBe(person.id);
});
https://phpunit.de/manual/current/en/test-doubles.html
public function testStub()
{
$mock = $this->createMock(DbHandle::class);
$mock->method('prolong')
->with($this->anything())
->with($this->person.id);
$book = new Book($mock);
$this->assertEquals('foo', $book->prolong());
}
Welke stukken kan ik op die manier testen...
in een Symfony project?
$bookRepositoryMock = $this->getMockBuilder(EntityRepository::class)
->disableOriginalConstructor()
->getMock();
$bookRepositoryMock->expects($this->once())
->method('find')
->will($this->returnValue($book));
$bookController = new BookController($bookRepositoryMock);
http://symfony.com/doc/current/testing/database.html
"Programmers should avoid writing code that looks like dog.getBody().getTail().wag();
The solution is to rewrite our example as dog.expressHappiness();
and let the implementation of the dog decide what this means."
Don't
Repeat
Yourself!
Anders gezegd zijnde:
"Duplication is the root of all evil"
You
Aint
Gonna
Need
It
Anders gezegd zijnde:
Schrap "Als dit er ooit komt en in geval van x..."
http://www.prato-services.eu/![]()