Presented by Larry Garfield (@Crell)
Evolution is possible!
Target Testable Code
"Testable" is a proxy for ...
If it's hard to test, you're doing it wrong
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
Refactoring is more a social/governancechallenge than a technical challenge
—Dwight Eisenhower
Are you refactoringor 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
Wrong
Developers are users... of code
The new code will be better
I'll believe it when I see it
c. 2008-2011
Throw HttpKernel in the middleand let the chips fall where they may
Make the old code feel clunkierin comparison
Shame the old code, not the old coders
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...
—General Robert H. Barrow, US Marine Corps
—Larry Garfield, 2011 and 2012 and 2013 and 2014
Work does not happen in spare time
— Tim Plunkett, Drupal 8 developer #2
Beware the bikeshed
Luck
Don't write unit tests
Drupal wrote its own testing framework
(Hey, it was 2008)
Having those tests saved our butts
Functional tests are for functionality... only
Pseudo-unit tests are worse than no tests
Sharing is caring
I don't want to maintain [my] crappy code!
So use someone else's
(Then they have to maintain it!)
— Beth Tucker-Long, Code Climate
Drupal never seriously considered writing a YAML parser
Refactor your app, don't replace it
Replace your component, don't refactor it
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
—Nathaniel Catchpole (catch), Drupal 8 Release Manager
—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 __construct(LanguageManager $language_manager) { $this->languageManager = $language_manager } 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 stonetoward Dependency Injection
Nothing is more permanent than a temporary solution
This takes commitment to address
Changesets that just remove legacy usemust be acceptable
Improving code without adding features must be acceptable
Time for just paying down debt must
Don't bite off more than you can chew
Finally!
Procedural code inside class keyword is still progress
class FormBuilder { public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) { $this->formValidator = $form_validator; $this->formSubmitter = $form_submitter; $this->formCache = $form_cache; $this->moduleHandler = $module_handler; $this->eventDispatcher = $event_dispatcher; $this->requestStack = $request_stack; $this->classResolver = $class_resolver; $this->elementInfo = $element_info; $this->csrfToken = $csrf_token; $this->themeManager = $theme_manager; } // ... 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; } }
/** * @deprecated */ function drupal_get_form($form_arg) { return call_user_func_array( [\Drupal::formBuilder(), 'getForm'], func_get_args() ); } /** * @deprecated */ function drupal_build_form($form_id, &$form_state) { return \Drupal::formBuilder()->buildForm($form_id, $form_state); } /** * @deprecated */ function form_state_defaults() { return \Drupal::formBuilder()->getFormStateDefaults(); }
Later removed entirely!
No program will ever be perfect, just make it better
Contain the crap
FormBuilder is gross, but injectableclass FormBuilder { public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) { $this->formValidator = $form_validator; $this->formSubmitter = $form_submitter; $this->formCache = $form_cache; $this->moduleHandler = $module_handler; $this->eventDispatcher = $event_dispatcher; $this->requestStack = $request_stack; $this->classResolver = $class_resolver; $this->elementInfo = $element_info; $this->csrfToken = $csrf_token; $this->themeManager = $theme_manager; } }
The code around it is fine
Know when to push for better, and when it's good enough
— @Getify
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?