Presentation by @JeroenDeDauw
bit.ly/static-code
What is wrong with this code?
public function doStuff() {
// ...
Logger::log( 'doing stuff during caturday is madness' );
// ...
DataStore::saveObject( $object );
// ...
}
public function doStuff() {
Logger::log( 'doing stuff during caturday is madness' );
DataStore::saveObject( $object );
}
public function doStuff() {
Logger::log( 'doing stuff during caturday is madness' );
DataStore::saveObject( $object );
}
public function testDoStuff() {
// Some bootstrapping code ...
$object->doStuff();
}
Logger::log and DataStore::saveObject are ALWAYS called
DataStore::saveObject( $object );
class DataStore {
public static function saveObject( $object ) {
$loadBalancer = LoadBalancerFactory::getInstance();
// ...
$database->update( /* ... */ );
// ...
Logger::log( 'done saving object' );
}
}
Lets fix it!
class Logger {
public static function log() {
// ...
}
public static function resetForTesting() {
// ...
}
}
Using an instance
class Logger {
public function log() { /* ... */ }
}
public function doStuff() {
// ...
Logger::getInstance()->log( 'doing stuff during caturday is madness' );
// ...
}
The SOLID solution
public function doStuff( Logger $logger, DataStore $dataStore ) {
// ...
$logger->log( 'doing stuff during caturday is madness' );
// ...
$dataStore->saveObject( $object );
// ...
}
Dependency Inversion Principle
public function __construct( Logger $logger, DataStore $dataStore ) {
$this->logger = $logger;
$this->dataStore = $dataStore;
}
public function doStuff() {
// ...
$this->logger->log( 'doing stuff during caturday is madness' );
// ...
$this->dataStore->saveObject( $object );
// ...
}
public function testDoStuff() {
$logger = new NullLogger();
$dataStore = $this->getMock( 'DataStore' );
$object = new SomeObject( $logger, $dataStore );
$object->doStuff();
// ...
}
The secret in testing is writing testable code.
-- Miško Hevery
What about this static call?
class Awesome {
public int calculateAwesomeness() {
// ...
// Clearly this can never be negative
return Math.Abs( $awesomeness );
}
}
Static calls to leaves in the call graph are less evil
Leaves can stop being leaves
class Awesome {
public static function calculateAwesomeness() {
// ...
$minAwesomeness = 9001;
return min( $minAwesomeness, $awesomeness );
}
}
And now min awesomeness becomes config stored in your db...
Often considered an anti-pattern
Should not be used all over
Global state is transitive
Global state
$GLOBALS['database']->saveStuff();
getDatabase()->saveStuff();
Database::saveStuff();
Database::singleton()->saveStuff();
ServiceRegistry::getService( 'database' )->saveStuff();
( new Database() )->saveStuff();
CC BY-SA 3.0, Jeroen De Dauw
Clone from GitHub or view at bit.ly/static-code