Presented by Larry Garfield (@Crell)
implements Huggable
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/governance
challenge than a technical challenge
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
Wrong
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
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 and 2014
Work does not happen in spare time
"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
Luck
Proper prior planning prevents piss poor performance
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!)
Writing code should not be the 1st response. Finding if shared code exists should be the 1st response.
Drupal never seriously considered writing a YAML parser
Refactor your app, don't replace it
Replace your component, don't refactor it
drupal_http_request()
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), Drupal 8 Release Manager
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 __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 stone
toward Dependency Injection
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
Time for just paying down debt must be acceptable
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
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;
}
}
The code around it is fine
Know when to push for better, and when it's good enough
"perfect is the enemy of good". yeah, but good enough is the enemy of good, too.
— @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?