Share
Embed
Transcript
75 slides
-
PRESENTED BY Patterns That Pay Off Design, behavior, and organizational
patterns that will make a real impact on your company and codebase Your Friend Matt @stauffermatt -
pattern 1. a regular and intelligible form or sequence discernible
in certain actions or situations 2. a model or design used as a guide in needlework and other crafts. noun -
Do this on every app no matter what; e.g. “floss
every day,” “avoid N+1 queries,” “avoid env() calls in non-config code” Preventative patterns Preventative vs. Reactive -
Feel/see the pain then choose the fix; e.g. “root canal
to treat cracked tooth,” “microservice to isolate slow adapter infrastructure,” “factory to normalize multiple exchangeable implementations” Reactive patterns Preventative vs. Reactive -
If all you have is a hammer, everything looks like
a nail. Preventative vs. Reactive -
If all you have is a hammer, everything looks like
a nail. command bus command Preventative vs. Reactive -
• You think you can predict the future • You
can’t predict the future • Source of process anti-patterns No precogs -
$ • When you try to predict the future, you:
• Commit to unrealistic timelines, leading to unhappy clients and unhappy devs • Over-architect to try to reduce Cost of Change • Become rigid, and can’t flex with new requirements • Instead, feel free to do the best work you can with what’s in front of you No precogs: the payoff -
• Establish and enforce standards • Code style • Code
organization • Trusted packages • Version control naming and branching strategies • Pull request Strategies • etc. Codebase Consistency -
I don’t want to be able to tell who wrote
the code just from looking at it. -
$ • Easier onboarding and less cognitive load when entering
a new codebase • Good patterns become ingrained easily into company culture and new-dev onboarding • Devs spend brainpower on stuff that matters Codebase Consistency: the payoff -
• Shell scripts for common tasks • Examples: • ./bin/init.sh
• ./bin/update.sh • ./bin/precommit.sh • ./bin/deploy.sh Bin scripts -
Bin Scripts #!/bin/bash # Sample bin/init.sh composer install cp .env.example
.env php artisan key:generate php artisan migrate —seed npm install npm run dev #!/bin/bash # Sample bin/update.sh composer install php artisan migrate npm install npm run dev -
$ • Easier onboarding and less cognitive load when entering
a new codebase • Less time wasted on “it works for me” or “did you remember to _____” • More likelihood developers will discover the steps for onboarding (devs don’t read docs ) Bin scripts: the payoff -
• No code can get merged to production without two
sets of eyes on it • One developer + one code reviewer • (OR) Two pair programmers Four Eyes * If any team members have more or less than two eyes, this number can be modified -
$ • Avoid the negative consequences of hyper focus •
“Reviewing brain” is often different from “writing brain” • Any two developers bring different perspective, broadening the team’s capabilities • Share load of responsibility for all new code introduced Four Eyes: the payoff -
• Review your own code diff before asking for review
• Less wasted Senior dev review BONUS TIP: Six eyes! -
• Self-documenting code # • … some code (e.g. complex
database queries) just takes time to grok what it does • … more code takes time to grok why it does what it does • Avoid // Show the resource • Aim for // Exclude the connections that match both user ID *and* service ID Document the weird -
$ • Faster comprehension of complex code • Less wasted
lines/visual debt on useless documentation • Less useless docs = people read docs more • Records business priorities Document the weird: the payoff -
• Agile Manifesto != The Agile Industrial Complex • “Agile”
is often waterfall, rebranded • Return to agility • “Gee, Spider-Man super is agile!” instead of “I’m a Certified Agile Scrum-Master 3000” • Stay light, respond quickly, adjust often • Militia vs. large army Lower-case agile -
$ • Agile promises • React quickly to new information/stimuli
• Deliver functional iterations early and often • Flexibility around different workers & different projects • Without Agile costs • No one saying “I can’t pull that because it will mess up the burndown chart” • Focus on delivering great software, not fitting a system Lower-case agile: the payoff -
• You’ve heard of API-first: design the API, consume it
with both JavaScript and mobile • You’ve heard of microservices: split your app into a million pieces so each is independent • Consider monolith first • API-first and microservices have their place • …but aren’t free • …especially on small teams Monolith first -
$ • Avoid API-first/microservice-first costs: • More complicated and duplicated
validation • More complicated and error-prone synchronization of testing data and expectations • More complicated branching and deploy strategies • Higher up-front development costs • Reduced flexibility Monolith first: the payoff -
• Don’t rely on dumps of production data for new
devs or testing • Build robust, randomized seeders • Check features under development on randomized seeds, not the narrower set of data-we-currently-have- in-production • Keep production data in production • Pro tip: reference production data to help you improve seeds Seeds, not dumps -
$ • Easier CI/CD • Easier testing • Easier onboarding
new users • Easier bug fixing • Less scared to make changes locally • Reduces fragility (less likely to only work if the data is a certain way) Seeds, not dumps: the payoff -
• The best place to start your tests is by
asking yourself: “what part of this app, if broken, would make me worried for my job?” • …“what’s most likely to break” • …“what do I have the least control over” • …“what are we about to refactor” • …“what would make my clients stress out” • …“what would make me stress out” Test to save your job -
$ • You get to keep your job • Testing
because it’s providing immediate value, not because someone told you you should • Most important tests first • You get to keep your job Test to save your job: the payoff -
• Reentrance: If a task is interrupted, it can be
restarted and completed successfully • Idempotence: A task can be called multiple times, without changing the side effects • Concurrence: More than one of a task can be run at the same time • Sequence Independence: The order of the tasks doesn’t matter Scalable tasks -
$ • Easy multi-worker spinup • No fears/stress about re-running
jobs or crons • Reduces fragility —less centering everything around one server being in one particular state Scalable tasks: the payoff -
• Once you start seeding, it’s tempting to seed everything
up front, once, for all your tests • It’s OK to seed some broad data up front—“company”, “user”—but uniquely seed the data under test in the test setup itself • Factory states and stories can be helpful here Seed each test -
$ • No magic numbers • No need to flush
work affected by previous tests • No “it works when we run this other test before but not when we don’t” • Data and tests on that data are in the same file, so they’re easier to connect and reason about Seed each test: the payoff -
• Catchall for “POPO (Plain Old PHP Object)” that does
stuff without being specifically tied to a design pattern or framework structure • Get creative with your naming (e.g. RetrieveMoviePoster) • Consider __invoke(): RetrieveMoviePoster() Service Classes -
$ • Shorter methods • “Separation of Concerns” • Easier
and clearer and more obvious testing • You get to say “POPO” more Service Classes: the payoff -
• Much app complexity comes from parsing HTTP requests •
See Laravel’s “Form Requests” for inspiration • More possibilities… Custom Requests -
View Data Composition: Presenters class SignUpRequest extends Request { public
function prepared() { // Do stuff here with the data } } // In controller: public function store(SignUpRequest $request) { $data = $request->prepared(); // ... etc. } -
$ • Smaller controllers • Easier testing • Separate repetitive
input logic (e.g. preparing data) Custom Requests: the payoff -
• Many oversized controllers/controller methods are because of the cost
of organizing data for a specific view • Presenter: Layer on top of other thing, often model, to give it more powers in views (like a decorator for views) • View model: View POPO for organizational purposes • Responsable: interface in Laravel for a PHP object of any class that can be converted to a response • View components: idea/package for Vue-component- style view data composition View Data Composition -
View Data Composition: Presenters class PostPresenter { public function __construct(Post
$post) { $this->post = $post; } public function presentPublishedAt() { return $this->published_at->format('M j, Y'); } // Use custom methods, *or* use a presenter package to get magic // Eloquent-style accessors } // In controller: return view('posts.show')->with('post', new PostPresenter($post)); -
View Data Composition: View Models class DashboardViewModel { public function
__construct(User $user) { $this->user = $user; } public function topTasks() { $overdue = $this->overdueTasks(); return array_merge($overdue, $this->fillTasksDueSoon(count($overdue))); } public function progressChart() { return [ 'percentage_complete' => $this->progressPercentageComplete(), 'date_markers' => $this->dateMarkers(), ]; } ... // In use: return view('dashboard')->with('vm', new DashboardViewModel($user)); -
View Data Composition: Responsables class DashboardViewResponse implements Responsable { //...
other prep the same as before in the view model public function prepareData() { return [ 'tasks' => $this->tasks, // etc. ]; } public function toResponse() { $data = $this->prepareData(); return response()->view('dashboard', $data); } // In use (in the controller): return new DashboardViewResponse($user); -
View Data Composition: Responsables class DashboardViewResponse implements Responsable { //...
other prep the same as before in the view model public function prepareData() { return [ 'tasks' => $this->tasks, // etc. ]; } public function toResponse() { $data = $this->prepareData(); if (request()->ajax()) { return response()->json($data); } return view('dashboard', $data); } // In use (in the controller): return new DashboardViewResponse($user); -
View Data Composition: View Components class NavigationComponent implements Htmlable {
public function __construct(Request $request, $backgroundColor) { $this->request = $request; $this->backgroundColor = $backgroundColor; } public function toHtml() { return view('components.navigation', [ 'activeUrl' => $this->request->url(), 'backgroundColor' => $this->backgroundColor, ]); } // In use: @render('navigationComponent', ['backgroundColor' => 'black']) -
$ • Slimmer controllers • Clearer grouping and composition of
data and its sources • Easier testing • Fun words like “Responsable” and “Htmlable” View Data Composition: the payoff