Presented by Larry Garfield (@Crell)
No legacy code
Less legacy code
What we really want is testable code
Evolution is possible!
Very powerful, but straining at the seams
How'd they do that?
Web Services and Context Core Initiative
Drupal needs to evolve, and quickly, from a first-class web CMS into a first-class REST server that includes a first-class web CMS.
Refactoring everything wasn't a goal, it was a methodology
Learn from our experience mistakes
Plans are worthless, but planning is everything.
—Dwight Eisenhower
Are you refactoring
or adding refactored features?
People need to see value
Developers need to see value
Management need to see value
Is refactoring valuable?
Code quality doesn't matter, only user experience matters
Fire anyone who says this
Developers are users... of code
The new code will be better
I'll believe it when I see it
See, it's not so scary
c. 2008-2011
Throw HttpKernel in the middle
and let the chips fall where they may
Make the old code feel clunkier
in comparison
Shame the old code, not the old coders
The right approach technically
and the right approach politically
may be completely different.
Clear messaging
Repeat your message
Everyone wants to hear the message themselves
Need to repeat it in different ways
It's a total waste of time... Do it anyway
There are costs to poor messaging...
Amateurs think about tactics, but professionals think about logistics.
—General Robert H. Barrow, US Marine Corps
Work does not get done in spare time
Context switching sucks
Give me 5 people 10 hours a week and I can do anything. Give me 50 people one hour a week and I can't do squat.
—Larry Garfield, 2011 and 2012 and 2013
"if you give 3 people 3 days to work together on something, they’ll get unbelievable amounts of work done."
— Tim Plunkett, Drupal 8 developer #2
Beware the bikeshed
Strong leadership, solid communication, luck
Proper prior planning prevents piss poor performance
Don't write unit tests
Drupal wrote its own testing framework
(Please please do not do this)
Having those tests saved our butts
Functional tests are for functionality... only
Pseudo-unit tests are worse than no tests
Sharing is caring
Modern PHP is all about sharing
It's way easier than it used to be
I don't want to maintain [my] crappy code!
So use someone else's
(Then they have to maintain it!)
Writing code should not be the 1st response. Finding if shared code exists should be the 1st response.
— Beth Tucker-Long, Editor-in-Chief, php[architect]
Drupal never seriously considered writing a YAML parser
Refactor your app, don't replace it
Replace your component, don't refactor it
(Especially cryptography!)
drupal_http_request()
We did our research!
s/drupal_http_request()/Guzzle/
If it's not broke, don't fix it
You want 100% green tests?
CI server or it won't happen
When you commit enough regressions, it gets harder to measure new ones.
—Nathaniel Catchpole (catch)
Make contributing easy
The Best is the Enemy of Good
—My dad (and Voltaire)
Idealism is a guide, not a rule
Incremental progress is progress
User a Service Locator
But Service Locators are eeeeevil!
Service Locator = passed DI Container
That's it
Remember what we said about small steps?
class Drupal {
protected static $container;
public static function setContainer(ContainerInterface $container = NULL) {
static::$container = $container;
}
public static function getContainer() {
return static::$container;
}
public static function service($id) {
return static::$container->get($id);
}
public static function entityManager() {
return static::$container->get('entity.manager');
}
// ...
}
function menu_menu_update(Menu $menu) {
menu_cache_clear_all();
// Invalidate the block cache to update menu-based derivatives.
if (\Drupal::moduleHandler()->moduleExists('block')) {
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
}
}
class BookManager {
public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
$language_interface = \Drupal::languageManager()->getCurrentLanguage();
// ...
$tree[$cid] = $this->bookTreeBuild($bid, $tree_parameters);
return $tree[$cid];
}
}
class BookManager {
public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
$language_interface = $this->languageManager()->getCurrentLanguage();
// ...
$tree[$cid] = $this->bookTreeBuild($bid, $tree_parameters);
return $tree[$cid];
}
public function languageManager() {
return \Drupal::languageManager();
}
}
class BookManager {
public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
$language_interface = $this->languageManager()->getCurrentLanguage();
// ...
$tree[$cid] = $this->bookTreeBuild($bid, $tree_parameters);
return $tree[$cid];
}
public function languageManager() {
return $this->languageManager;
}
}
Service locators are a stepping stone
toward Dependency Injection
Break things in steps
Big refactoring breaks APIs
Deal
function cache_get($cid, $bin = 'cache') {
return _cache_get_object($bin)->get($cid);
}
function cache_get_multiple(array &$cids, $bin = 'cache') {
return _cache_get_object($bin)->getMultiple($cids);
}
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
return _cache_get_object($bin)->set($cid, $data, $expire);
}
/**
* @deprecated
*/
function cache_get($cid, $bin) {
return cache($bin)->get($cid);
}
function cache($bin = 'cache') {
return \Drupal::cache($bin);
}
function something() {
$item = cache_get('menu', 'a_key');
}
function something() {
$item = cache()->get('a_key');
}
function something() {
$item = \Drupal::cache('menu')->get('a_key');
}
Nothing is more permanent than a temporary solution
This takes commitment to address
@deprecated
(Never add uses)E_USER_DEPRECATED
?Changesets that just remove legacy use
must be acceptable
Improving code without adding features must be acceptable
Don't bite off more than you can chew
Procedural code inside class keyword is still progress
class FormBuilder {
public function __construct(ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, UrlGeneratorInterface $url_generator, TranslationInterface $translation_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
$this->moduleHandler = $module_handler;
$this->keyValueExpirableFactory = $key_value_expirable_factory;
$this->eventDispatcher = $event_dispatcher;
$this->urlGenerator = $url_generator;
$this->translationManager = $translation_manager;
$this->csrfToken = $csrf_token;
$this->httpKernel = $http_kernel;
}
// ...
public function getFormId($form_arg, &$form_state) {
// 20 lines of fugly here.
}
public function buildForm($form_id, array &$form_state) {
// 100 lines of code/comments here
return $form;
}
}
function drupal_get_form($form_arg) {
return call_user_func_array(
[\Drupal::formBuilder(), 'getForm'],
func_get_args()
);
}
function drupal_build_form($form_id, &$form_state) {
return \Drupal::formBuilder()->buildForm($form_id, $form_state);
}
function form_state_defaults() {
return \Drupal::formBuilder()->getFormStateDefaults();
}
function drupal_rebuild_form($form_id, &$form_state, $old = NULL) {
return \Drupal::formBuilder()->rebuildForm($form_id, $form_state, $old);
}
No program will ever be perfect, just make it better
Contain the crap
class FormBuilder {
public function __construct(ModuleHandlerInterface $module_handler, KeyValueExpirableFactoryInterface $key_value_expirable_factory, EventDispatcherInterface $event_dispatcher, UrlGeneratorInterface $url_generator, TranslationInterface $translation_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) {
$this->moduleHandler = $module_handler;
$this->keyValueExpirableFactory = $key_value_expirable_factory;
$this->eventDispatcher = $event_dispatcher;
$this->urlGenerator = $url_generator;
$this->translationManager = $translation_manager;
$this->csrfToken = $csrf_token;
$this->httpKernel = $http_kernel;
}
}
The code around it is fine
Know when to push for better, and when it's good enough
Interfaces FTW
Refactor the fugly to be smaller
If we can do it, so can you
Senior Architect, Palantir.net
Making the Web a Better Place
Keep tabs on our work at @Palantir
Want to hear about what we're doing?