We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Keynote by Searls

00:00

Formale Metadaten

Titel
Keynote by Searls
Serientitel
Teil
8
Anzahl der Teile
86
Autor
Lizenz
CC-Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Unported:
Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben.
Identifikatoren
Herausgeber
Erscheinungsjahr
Sprache

Inhaltliche Metadaten

Fachgebiet
Genre
35
SoftwaretestDelisches ProblemHypermediaSoftwarePhysikalische TheorieMultiplikationsoperatorSchlussregelComputerspielComputeranimation
Wort <Informatik>Coxeter-GruppeProgrammierungMultiplikationsoperatorFramework <Informatik>StandardabweichungProgrammiergerätFormale SpracheNP-hartes ProblemPhysikalisches SystemKontrollstrukturEinfach zusammenhängender RaumDatenstrukturKryptologieAnalysist-TestGruppenoperationSoftwareentwicklerGebäude <Mathematik>Prozess <Informatik>StereometrieDatenflussInformatikBenutzerbeteiligungAuflösung <Mathematik>CodePersönliche IdentifikationsnummerBootenProdukt <Mathematik>Dienst <Informatik>EntwurfsmusterXMLComputeranimation
Gemeinsamer SpeicherDienst <Informatik>ProgrammierungHasard <Digitaltechnik>ResultanteEinfache GenauigkeitInformatikGüte der AnpassungProdukt <Mathematik>Wort <Informatik>ComputeranimationXMLDiagramm
CodeProgrammierungSchreib-Lese-KopfKreisflächeProgrammiergerätMomentenproblemMultiplikationsoperatorEinfacher RingSchlussregelComputeranimation
Exogene VariableProgrammiergerätGeradeFrequenzKartesische KoordinatenWort <Informatik>Computeranimation
GruppenoperationTabellenkalkulationProgrammiergerätProgrammierungProzess <Informatik>InternetworkingPunktProdukt <Mathematik>XMLComputeranimation
SoftwareSoftwareentwicklerFormale SpracheProgrammierungKonstruktor <Informatik>Prozess <Informatik>GeradeSchätzfunktionFramework <Informatik>ProgrammbibliothekProgrammiergerätProgrammierumgebungGleitendes MittelComputerspielXMLUML
MultiplikationsoperatorRückkopplungSampler <Musikinstrument>GruppenoperationSoftwaretestProgrammiergerätSchreib-Lese-KopfArithmetische FolgeSoftwareentwicklerEinsNegative ZahlProzess <Informatik>DämpfungOrtsoperatorAggregatzustandXMLUMLJSONComputeranimation
IndexberechnungTypentheorieEinsPhysikalisches SystemWort <Informatik>Softwaretestp-BlockCMM <Software Engineering>XMLComputeranimation
Geneigte EbeneProgrammiergerätResultanteSoftwaretestSelbst organisierendes SystemDatenverwaltungVorzeichen <Mathematik>DistributionenraumSymmetriebrechungXMLComputeranimation
SpieltheorieEin-AusgabeArithmetischer AusdruckPhysikalisches SystemSoftwaretestVirtualisierungZahlenbereichFunktion <Mathematik>SprachsyntheseGruppenoperationVektorpotenzialE-MailChatten <Kommunikation>ComputeranimationXML
Komplex <Algebra>FunktionalRechenschieberDreiCASE <Informatik>CodeProjektive EbeneInformationSensitivitätsanalyseAdditionTexteditorMereologieBetrag <Mathematik>XMLComputeranimation
TelekommunikationGeradeGeneigte EbeneProdukt <Mathematik>PunktOrdnung <Mathematik>Test-First-AnsatzCodeProgrammiergerätMereologieMAPSpeicherabzugRechter WinkelDatenbankGamecontrollerE-MailTermPerspektiveRelationentheorieProzess <Informatik>Ein-AusgabeKontextbezogenes SystemProgrammierungFunktion <Mathematik>NP-hartes ProblemRechenwerkElektronische UnterschriftComputeranimation
Geneigte EbeneBeweistheorieCodeLoopResultanteCodierungMereologieGruppenoperationRandomisierungSelbstrepräsentationBimodulTupelXMLComputeranimation
Wurzel <Mathematik>Test-First-AnsatzSoftwaretestEinsRadiusKugelElement <Gruppentheorie>RechenwerkCodeEinfache GenauigkeitMAPCASE <Informatik>HypermediaProgrammbibliothekShape <Informatik>HoaxVorzeichen <Mathematik>Computeranimation
Elektronische PublikationRechenwerkSoftwaretestArithmetische FolgeKontrollstrukturBitMereologieZweiXMLComputeranimation
Betrag <Mathematik>Physikalisches SystemMereologieOpen SourceCodeMultiplikationsoperatorProjektive EbeneWiderspruchsfreiheitTypentheorieDrucksondierungSymmetrische MatrixFramework <Informatik>SoftwareSymmetrieDruckverlaufGebäude <Mathematik>ImplementierungRechter WinkelComputeranimation
RechenwerkFunktionalSoftwaretestProgrammiergerätSoftwareentwicklerQuelle <Physik>MusterspracheTexteditorAnalysisXMLComputeranimationBesprechung/Interview
AnalogieschlussProgrammierungElektronische PublikationDokumentenserverCompilerWald <Graphentheorie>GraphRekursive FunktionPunktDienst <Informatik>RechenwerkKonstruktor <Informatik>Faktor <Algebra>URLMultigraphDifferenteSystemaufrufDiagrammSoftwareentwicklerInterpretiererTelekommunikationCodeSoftwarePufferüberlaufKnotenmengeFunktionalPuls <Technik>OrtsoperatorAntimaterieVideokonferenzDemoszene <Programmierung>Objekt <Kategorie>Komplex <Algebra>Anwendungsspezifischer ProzessorInstantiierungGemeinsamer SpeicherPerspektiveDreiecksfreier GraphDatenstromEntwurfsmusterFormation <Mathematik>Ausdruck <Logik>
RechenwerkObjekt <Kategorie>Rechter WinkelGeradeTopologieGraphDienst <Informatik>VerzeichnisdienstNebenbedingungElektronische PublikationWellenpaketMultigraphRelativitätstheorieInteraktives FernsehenBeobachtungsstudieComputeranimation
Produkt <Mathematik>FunktionalKonfigurationsraumMultiplikationsoperatorKomplex <Algebra>SoftwareSchlussregelFramework <Informatik>PaarvergleichPhysikalisches SystemGebäude <Mathematik>ProgrammierungProgrammierstilSchreiben <Datenverarbeitung>Projektive EbeneApp <Programm>Analytische FortsetzungComputeranimation
ParametersystemEndliche ModelltheorieRechenwerkObjekt <Kategorie>PunktBimodulEntscheidungstheorieTabelleKomplex <Algebra>SchlussregelQuick-SortWiderspruchsfreiheitWeg <Topologie>BildschirmmaskeTypentheorieCodeComputeranimation
FehlermeldungCodePhysikalisches SystemKartesische KoordinatenApp <Programm>Projektive EbeneMultiplikationsoperatorSchwingungGlobale OptimierungKomplex <Algebra>WiderspruchsfreiheitBetragsflächeEntscheidungstheorieMathematikMereologieWurm <Informatik>FokalpunktEinfügungsdämpfungComputeranimation
DruckverlaufSoftwaretestMessage-PassingSoftwareCodeAutorisierungMetrisches SystemMultiplikationsoperatorInformationsverarbeitungHinterlegungsverfahren <Kryptologie>CASE <Informatik>XMLComputeranimation
DruckverlaufAnalogieschlussServerResultanteProzess <Informatik>DatensichtgerätDrucksondierungProdukt <Mathematik>CodeXMLComputeranimation
Data MiningGefrierenSoftwaretestMessage-PassingProgrammiergerätMultiplikationsoperatorRechenwerkObjekt <Kategorie>KonditionszahlDreiecksfreier GraphProdukt <Mathematik>Anwendungsspezifischer ProzessorKomplex <Algebra>Rechter WinkelDatensatzPhysikalischer EffektPhysikalisches SystemEinsPunktFunktionale ProgrammierspracheMathematische LogikSchreib-Lese-KopfToter WinkelAutomatische IndexierungAdditionGraphFormale SpracheQuellcodePlastikkarteKomponententestVererbungshierarchieDatenverwaltungMathematikSoftwareschwachstelleBitFunktionalLuenberger-BeobachterEin-AusgabeZahlenbereichExogene VariableKartesische KoordinatenSpeicherabzugNichtlinearer OperatorSoftwareentwicklerCASE <Informatik>ÄhnlichkeitsgeometrieGruppenoperationVorzeichen <Mathematik>CodeLeistung <Physik>Hash-AlgorithmusTypentheorieIdeal <Mathematik>AbstraktionsebeneSystemaufrufFlächeninhaltWurzel <Mathematik>Web-SeiteElektronische PublikationAttributierte GrammatikTopologieProgrammfehlerFunktion <Mathematik>ProgrammierungElektronische UnterschriftEinfache GenauigkeitHyperbelverfahrenComputeranimation
MathematikCoxeter-GruppeRechenwerkCodeSoftwaretestMathematische LogikProzess <Informatik>DifferenteKartesische KoordinatenMultiplikationsoperatorGebäude <Mathematik>MereologieFunktionalHinterlegungsverfahren <Kryptologie>TopologieMessage-PassingComputerspielDatenverwaltungMAPBildschirmmaskeProgrammierungMerkmalsraumEndliche ModelltheorieSchlussregelTelekommunikationBitArithmetisches MittelBetafunktionHilfesystemRuhmasseXMLComputeranimation
CodeWasserdampftafelProgrammierstilBetrag <Mathematik>MultiplikationsoperatorGesetz <Physik>Kartesische KoordinatenZellularer AutomatCASE <Informatik>SoftwareentwicklerFlächeninhaltGrenzschichtablösungComputeranimation
GamecontrollerBitFreewareVorzeichen <Mathematik>Open SourceObjekt <Kategorie>Große VereinheitlichungIntegralMultiplikationsoperatorGraphNegative ZahlWrapper <Programmierung>KonzentrizitätEnergiedichteExpertensystemCodeInternetworkingRichtungArbeit <Physik>Kategorie <Mathematik>RadiusSoftwaretestRechenwerkSoftwarewartungSweep-AlgorithmusGebäude <Mathematik>ReibungswärmeSchreib-Lese-KopfKonstanteComputeranimationXML
SoftwareentwicklerSystemaufrufWrapper <Programmierung>SoftwaretestDatenbankCodeExogene VariableReelle ZahlRechenwerkIntegralValiditätCASE <Informatik>KreisringKomponententestVerzweigendes ProgrammMathematikTaskInteraktives FernsehenUngleichungElektronisches WasserzeichenHypermediaComputeranimation
ComputerspielPhysikalisches SystemMAPAbstraktionsebeneWrapper <Programmierung>Objekt <Kategorie>GruppenoperationDatensatzTypentheorieBenutzerschnittstellenverwaltungssystemTransformation <Mathematik>MultiplikationsoperatorOpen SourceBitProgrammbibliothekGamecontrollerInterface <Schaltung>Parallele SchnittstelleKomplex <Algebra>SoftwaretestAbstandEnergiedichteGrenzschichtablösungFortsetzung <Mathematik>MathematikImplementierungRichtungComputeranimation
GrundraumCodeSchreiben <Datenverarbeitung>Lesezeichen <Internet>Projektive EbeneGeneigte EbeneDatenverwaltungProgrammiergerätRechenwerkSummierbarkeitGewicht <Ausgleichsrechnung>MultiplikationsoperatorXMLComputeranimation
UnordnungGemeinsamer SpeicherVektorraumDemoszene <Programmierung>Prozess <Informatik>ProgrammiergerätGruppenoperationGeradePhysikalisches SystemSoftwareentwicklerMittelwertTreiber <Programm>Einfach zusammenhängender RaumDifferenteBildgebendes VerfahrenMultiplikationsoperatorGewicht <Ausgleichsrechnung>EntscheidungstheorieWhiteboardBruchrechnungNormalvektorXMLUMLComputeranimation
Arithmetische FolgeWeb-SeiteProzessautomationSoftwaretestMultiplikationsoperatorWeb SiteE-MailSoftwareentwicklerDelisches ProblemProgrammiergerätTypentheorieDualitätstheorieAdditionArithmetisches MittelProgrammierungAutomatische HandlungsplanungComputeranimationXML
XML
Transkript: Englisch(automatisch erzeugt)
Your name is Justin. Feel free to call me either. This is what my face looked like in 2011, and thanks to how social media branding works, I'm now stuck with it forever.
I work for a company called Test Double. We're a software agency who's on a mission to improve how the world writes software. You can learn more about us up at that URL. The title of this presentation is How to Program, and it's a rumination on the word workflow, two-part
word, work being what programs are, their structure and behavior, flow being how we program our thoughts and actions. And when I look back on my experience learning as a computer science student, they taught me things like data structures and P versus NP and big O analysis and cryptography, and not very much about how to think or how to work.
And boot schools nowadays are actually analogous, even though they're more market practical skills like web standards and system tooling and languages and frameworks, not so much how to think through problems and solve stuff and really write code. And you might think that's the job of thought leaders, because the word thought is right there, but really thought
leaders, when they're talking about design patterns or solid principles, even when they talk about agile and test-driven development, those are nice because they describe work activities, but it's pretty discreet and mostly about how people interact, not so much how to think through things. So it's reasonable to ask, you know, when do we actually learn flow as programmers? Who teaches us how to think?
And if you're lucky, you know, 10 years into your career, you'll stumble upon or somebody will show you, you know, the only productivity tip that any of us have ever been taught. Where you work for 20 minutes and you take a three-minute break, it's really awesome.
But honestly, it kind of feels like you put 10 years of hard service in to get a, you know, $4 plastic pin. It's kind of insulting that that's the best that we have to offer. So sure, somehow we all learned what programs are, but I hazard a guess that most of us, nobody ever really taught us how to program.
Look no further than a Google search, how to program, and you get a whole bunch of terrible results, starting with the traditional way of teaching people how to program. You start with nothing and then somebody shows you a completely finished example, the finished product, what the program should be, and then as for connecting it, it's good luck, have fun.
And every single computer science assignment that I had in college, you know, really resembled the how to draw an owl comic, where you start with two circles and then go draw the rest of the owl. And I spent entire weekends cooped up in a lab trying to figure out, you know, staring at a blank editor, no idea how to write code.
And it was that moment that I realized that programming is almost a philosophical activity that happens mostly in our heads. Of course, we've now innovated quite a lot in programmer education since I was in college. Now we, instead of just one big finished example, we've broken it up into two or three steps over the course of a book or a screencast.
But very rarely does the prose or the explanation actually explain the thinking of how to make that thing more real. It might take years before you're able to imitate, you know, even an example application from a book.
But that word imitation stands out because I think that most of us are just imitating other programmers. We see somebody successful or well known, and we just try to do things like they seem to be doing things, and that's how we kind of learn and get by. You can see that this is endemic in our society as programmers because we're really bad at how questions. If I ask, how do I know when to create a new method? When should I break this thing up into more than one thing?
You know, you get really unsophisticated responses, like method should be about three lines long. My wife, she likes telling this story, when she was in first grade, Becky, she was told by a teacher that sentences were two lines long,
and so then dutifully for the next several years until she was corrected, she just stamped a period at the end of every other line. And that's funny because she's not an adult, and yet here we are with these unsophisticated ideas like,
ah, method should be about three lines long and no ability to communicate above and beyond that. But let's say in spite of all of this, somehow you write a really good program one day, and you're really proud of it and really happy, and I ask you, okay, so what actions were productive or unproductive that led you to that point? Or what thoughts led you there? Which thought processes were successful or unsuccessful?
Would you be able to answer those questions? Most of us wouldn't be able to, and it leads to rampant insecurity from how we educate programmers to the work that we do to the colleagues that we keep in the overall industry. Now, 99% of the work that I've done as a professional programmer could be boiled down as a business person trying to get a spreadsheet onto the internet.
And yet it's taken me 10 years or so to even become a merely competent programmer. That's clearly something's wrong in how we teach people to program. And this industry is 60 or 70 years old, but we're still searching for silver bullets. We always are externalizing the problem and hoping the next language or
framework or library or process is going to suddenly make programming explicable. And it never works out. And think about that situation, you know, where everyone's either making stuff up as they go or pretending they understand it. Who's going to succeed in that environment? It's genuinely brilliant people and people
with the overconfidence of having been told that they're brilliant their whole lives. So imagine that you don't look like other programmers and you walk into a room and you lack that privilege of having been told that you're brilliant by society. This is a terrifying line of work to walk into because no one can actually explain how to program. And I think that if you want to make programming a more diverse and inclusive industry, we really need to solve this.
And it's obvious that the industry has no idea how software works because they're constantly analogizing it to literally any other industry. Like construction or like design or manufacturing.
Because of that they control the handful of things that they do understand. Like estimates and when people work and where they work instead of like the true none of it which is like how we think as software developers and how we solve problems. So how do we fix it?
Well fortunately yesterday at the keynote DHH offered us one solution. But I'm going to talk about a different one. I'm going to talk about feedback loops because programmers through compilation and through testing were used to establishing feedback loops to make forward progress.
And we can do the same things inside of our heads improving as developers. We're going to practice this today by reflecting on the actions we take and whether you're successful or not and how to improve our actions. You can do the same thing for feelings and actually reflect on your emotional state. So you can reinforce positive emotions or mitigate negative ones. And spoiler alert, you can actually think about thinking and produce better thought processes that turn out to be more productive.
This is really the path to programmer enlightenment but I realize that we're starting from scratch here. We got to walk before we can run and ask yourself what do we do
with teams that are so kind of emotionally immature they struggle to even talk about feelings. Well we hand them crappy personality tests like the Myers-Briggs Type Indicator. If you're not familiar with the Myers-Briggs, just know that it's the worst type system. The reason we rag on it is because it puts people into these silly buckets like ENTJ and ISFP.
And the implication is that there's only 16 types of people out there but we know that there's much, much more. But when you're starting from zero, 16 start sounding pretty good. So that's why today I'm pleased to announce the Searles-Briggs Type Indicator.
And instead of pontificating to you today about how to program and dictating that this is the magical way, this is the silver bullet, instead I'm just going to humbly take my own test, show you how I feel and my inclinations and my personality, and what I've done over the course of my career to reflect and improve and result in better outcomes as a programmer.
To do that we need an example feature, so let's make one up. Like I mentioned, I work at a company, Test Double, and we have always been a distributed company, but we're still learning that that does not mean evenly distributed. So if you've got a flat organization you might think that there's all these spontaneous relationships that form, but of course that's not accurate. We all have our assigned pairs and we all phone home to an account manager.
Our org chart looks like one of those 1980s suction cup ball things. But there's nothing wrong with that per se, unless say two people on the team both really want to learn Elm. But there's nothing systemically that's going to get those two people talking together necessarily. So somebody raised the idea, why don't we have virtual coffee dates on the team
and just randomly assign people to talk to each other that otherwise wouldn't be. There's actually an expression from math, it's called the handshake problem, which just calculates the number of potential relationships of any group of people, and you see that there's just tons of them. And so what the system should do is just send an email every week
to pair up people who might not otherwise be talking to one another and tell them, go spend 15 minutes on this Google Hangout URL and chat about something, it doesn't have to be about work. And so we're going to put my test to the test and build this feature together this morning as a group. And the first bucket that I'm going to put us in is sensitive versus fearless. And the first question is, I prefer hearing all requirements up front,
even if I can't tackle them all right away. I strongly disagree with that. I get overwhelmed really easily. Two, adding to a long function feels like more code just won't fit. Absolutely. Once I hit a certain amount of complexity, I can't imagine adding another pace.
Three, I look forward to being assigned to new projects and teams. No way. New projects give me night sweats. Four, I often feel paralyzed while staring at a blank editor screen. Yeah, I already admitted to that. So does that make me sensitive or fearless? Pretty obvious in this case. I'm sensitive. A big part of being sensitive is that I get overwhelmed easily.
So think about this feature and all I've got to do. You know, create pairs, great, that's not so hard, but I do have to go and send the email and I also have to look up all these people from a database. But I've got to be careful not to repeat, so I've got to randomize the pairings. And I've got to not repeat week to week either, which means I also have to persist them.
So there's a lot of work. So I don't want to think about all that. I just want to focus on the core problem. So my inclination is to just do that, put a unit around it, think hard about the problem, I can do my little test-driven development thing, and I'm feeling really good about that unit of code. So then I go to the controller that's going to call it, and I realize that the method signature doesn't quite line up.
Or somebody else might say, hey, this unit you just made is doing all this extra redundant work that's actually handled elsewhere. My inclination was to overcome that paralysis that I felt and find some productivity by just putting on blinders. And I fell into that rabbit hole often enough that I had to think and reflect and actually I've inverted how I work as a programmer. So what I just showed you is often called bottom-up programming, but now I practice top-down.
It works better for me. You might also call it outside-in, where I think from the perspective of the controller or the top-level entry point, and I start there and I ask, what do I need? Well, I need something to create these pairs. And when your brain is focused that way, the caller knows exactly what inputs are available, what output it's going to want.
It's also a way to minimize waste, because it has the broader context in terms of what guard clauses need to be inserted or can be omitted. The other aspect of being sensitive is that I'm really afraid of failure. And when you work outside-in, you do have to juggle all these different concerns at once, which can, again, be overwhelming.
And so what I try to do is just rush into solving it. Like, my inclination is just to open up an editor and prove that I can write this code. So I start with a module and I make a method. I go and load up a bunch of users. I loop over them. I skip anyone who's already had their hand shaken. Otherwise, I'll go and add a tuple of a representative pairing, and then I'll go and slam it through some mailer.
And now I feel really good, because I just proved I could do it. And someone will remind me, you remember, you've got to randomize it, and you don't want to leave out the 15th person every week. And you've also got to prevent repeats from happening week to week. And now I'm terrified because this is already really dense. I don't know how I'm going to make it more complicated. And I'm very familiar with this corner of the room as a result.
So I keep paying myself into this corner, and the root causes fear. I'm afraid of failing. I'm afraid of big things that I don't know how to break down. So my solution is avoid that fear by breaking things down in a systematized way. Give myself some help. And I've been practicing over the last few years iterating on an approach to test-driven development that I call discovery testing.
And it's really an effort in breaking big, scary problems into smaller, more manageable ones. It works with, I start with just a test of that top-level thing. So I write a single test case, I invoke the thing, and then I ask myself a crucial question. What's the code that I wish I had, that I could defer and hand this work out to?
Well, something to find these hands, something to determine who shakes whose hand, something to mail them and then persist them. Then I use my testable library, which in Ruby is GIMME. So it creates these four fake things, and I set up a stubbing. So I say, hey, when finds hands is called, it gets you this thing to symbolize the hands, and if I pass that to the thing that determines the shakes, another stubbing will give me these handshakes.
This is all the test setup I need to be able to actually assert that I mail out all those handshakes and that I persist them. Now, this is an unusual-looking test to a lot of you, I'm sure, but what it does is it perfectly specifies the behavior of that top-level unit. So I get this test to pass, and I never have to worry about that top level again.
In fact, if you list out all the files that shake out from just getting that test to pass, now I have a pretty clear work cut out for me. I know exactly what I need to do. I can start making forward progress. So I start off with a big scary thing, but as I build that test, I identify the units that I would need to actually carve out the work,
and instead of one big scary thing, I now have four more digestible problems that I can focus on. And if any of them are scary, I now have in my back pocket a tool that I can use to reduce and break things up even further. The second bucket I'm going to talk about is inventive versus aesthetic, so some quiz questions to determine my type.
Question one, it's more important to build the right thing than to build the thing right. Eh, these are both important, but I'm more implementation-focused. Question two, I love experimenting with new tools, frameworks, and build systems. Not at all. I do spend a lot of my time in open source here, but it's expressly so I don't have to worry about it at work.
Question three, I strive to write visually appealing code down to syntax and symmetry. Absolutely. I don't know why, but I really like pretty symmetrical code. Question four, it's boring when all the code in a project is structured similarly. I disagree. I really like consistency in code. So does that make me inventive or aesthetic? Well, I think it makes me a little aesthetic.
And one part of being aesthetic is I have refined taste. Now the problem with taste is that nobody knows what it is. Until somebody on your team says, you know what, I'd prefer a 300-line function to all these well-named little small units that you keep creating. And then my face looks like this. That's taste.
Why do programmers develop taste? Well, it's obvious. When you're staring at a blank editor screen, there are infinitely many ways to solve any given problem. We need something to constrain ourselves.
Some patterns to follow to just not be stuck in analysis paralysis forever. And that's why I think that prose is a much better analogy to writing software than construction is. In fact, I think that programming is just communication to the next developer who's going to pick it up. And it's just hard because it has to happen through this filter that's just formal enough for an interpreter or a compiler to understand.
And so when I hear this feedback, I reflect and I take it to mean it as a critique that maybe I'm writing code that's meant to be read by myself as opposed to another human. Because the other developer, they're not in the room. So I tend to write code that I think that I would want to read.
And this leads to self-centered design. So if I just write a book on design patterns, I'm going to create a bunch of units like services and factories and repositories. And I'm going to feel really brilliant having done all this. But then somebody else could list out all of the files I just created. And one big listing, from their perspective, it's going to be impenetrably complex. It's a forest of all these objects.
No idea how things are going to work. And so now when I hear that critique, they've got a pretty good point. And I found this happen on team after team. And what I mainly realized is I've got to start working differently to make my code more discoverable and approachable for other developers. And now I do things much differently. It started with realizing programs are directed graphs.
So all of these units are really kind of nodes in this graph. And all of the edges or vertices are function calls. So the locator calls the service, which calls this repository, which calls this mapper, which instantiates hands. Service also calls this factory, which calls that thing and that thing. And we already, because we think of programs this way, we have some taste.
We have some constraining principles that we share. Like, for instance, if this repository were to call this service, we would all, wait a second, that's a dependency cycle. We'd realize there's a risk of an infinite recursion or a stack overflow there. And so most of the programs that we write as developers try to be acyclic diagrams. And remember, my purpose here is just to prove that I'm right.
I want to tell this person, no, small units is better than 300 lines objectively. And to do that, I thought maybe there's a liberating constraint. Maybe if I refine my taste further, I can solve this problem. And where I landed was, I want to express all of my features, not as just general graphs, but as trees.
Because a tree is just a special subtype of a graph. You can take this exact same menagerie of units and organize it in a tree shape, where you have your value objects on the left and the future behavior on the right. And now, if it's true that I have any needless indirection, it stands out in a tree because it's just got one child. So that service locator, yeah, I can probably get rid of that.
And if somebody asks, you know, what they're looking for is how to compare two hands, they can search the tree much more easily and quickly than just a gigantic directory of files and find what they're looking for. So it's more discoverable. The other aspect of my aesthetic is I'm a minimalist. So when you look at our app, you see it's a car.
It drives. You think about what its function is and why it exists. When I look at our app, all I see is the clutter and the mess. And as a minimalist, my productivity goes up when things are tidy and symmetrical and terse, and it goes down when things are cluttered or inconsistent. And what the software is supposed to be doing is its essential complexity.
Everything it actually does is its incidental complexity, the other stuff. Think of everything that goes into writing a program. You're writing deploy tooling and app config and dependencies and framework appeasement. Then of course there's whatever the app was supposed to do. There's style rules, continuous integration, build systems, and then of course unnecessary stuff.
As a minimalist, I'm the person on the project who's always chiseling away to try to minimize the amount of incidental complexity in the system and trying to maximize the time that the team spends on whatever is really important. That means I'm always tweaking my code style. Like maybe I'd start writing feature behavior inside of my model objects, but then separate them out into separate units,
but then maybe make them callables, but maybe ultimately land at module methods. These are all fine, you can debate the finer points, but at the end of the day they're really six of one, half a dozen of the other kinds of arguments. And they represent a sort of trap. Because earlier I said style rules, debating style is a type of incidental complexity.
Because style is subjective, and so it changes around arbitrarily. And arbitrary decisions breed inconsistency, and oh it turns out that inconsistency is another form of incidental complexity. And so, oops, because I thought Monday I organized code this way, and on Tuesday this way, and Wednesday that way,
and then by Thursday I decided this is the best way to organize code. Everyone's going to get mad at me, because I just littered 36 custom little styles all throughout the system. So my temptation to continuously be improving stuff actually breeds inconsistency and creates bigger messes elsewhere. So I reflected on how I think what would happen is I'd chase the local
optimization at the expense of like the global optimization of what's best for the overall project. And I had to learn to avoid this oscillation in my design, and it really required me to just realize I'd spend the entire project spinning my tires. And so instead I decided I need to just lock in these decisions and say we're just going to do things this
style, whether or not it's better or worse, it doesn't matter, so we can try to carve out time to be productive. And as I got better at that, at flexing that muscle, I could recognize where I'm spinning my wheels earlier and spend more of my time being productive. It was a way for me to both be minimalist, but also really consistent in my applications.
Even when it meant hewing really strongly to arbitrary decisions of things that didn't really matter. And when you do that, especially with your incidental complexity, it brings the essential complexity into sharper relief so you can all as a team really just focus and spend more time on what your app really needs to do. The third bucket is naive versus leery.
Question one, publishing metrics like code coverage is always a good idea. And I think radical transparency often backfires. Question two, writing good commit messages today will pay off in the future. Secret, I don't actually read commit messages. Three, software teams will make smarter use of time under pressure.
Disagree, I think pressure kills cognition. Four, software is generally improving over time and we are not doomed. So does that make me naive or leery?
I'm starting to realize this is a pretty obvious test in my case, I'm leery. It all starts with my distrust of all of you. Because most teams operate in a pressure cooker. They're under pressure to get as much stuff done as fast as possible. And as a result, their brains turn off and it results in really, really bad outcomes.
In fact, it's not very fair to pressure cookers because pressure cookers serve a useful purpose. So I'll find another analogy where we're just being squeezed for all of the Ruby and JavaScript that we're worth.
And again, not being creative, not resulting in good code. One person on teams I don't trust is I don't trust product owners. I love them, but I don't trust them. Back in the waterfall days, you know, a product owner would be able to specify 300 bullet points of everything that they ever wanted. And then force them upon us and we'd say, hey, this is awful.
But you know, in a way, at least it was honest. They got to articulate everything that they wanted up front. It was just us who didn't know how to handle that much complexity. Scrum and agile stuff kind of gave us a little bit of a backbone. And we said, no, sir, you only get one index card at a time. And in fact, that's going to be 20 points. And I made up what points mean.
And so naturally, then it becomes a debate of, no, I think it's five points. And any system can be gained. And really savvy product owners I know are really good at this. And you're like, oh, you know, this is not that complex. It's just a little cartoon whale. You guys can do that, right? You have time. And we'll say, oh, yeah, sure, we can do that. And then we realize it's much more complex than we really thought.
And by then, they're out to lunch and we're left holding the bag. So I don't trust product owners. Of course, someone I trust even less than product owners is other developers. And it's because on day one of a feature, we neglect to realize a very basic fact. Our brains can only hold so much stuff in them at once.
And so I think the developers have this bias towards sizing features to just whatever number of things they can hold in their head at a time. And naturally, the conclusion that we draw about how big our objects and methods should be is the same size. Because then that way, we can just put all the things in one place.
It seems like the simple solution. But then a month passes and somebody says, hey, you need to add a couple additional aspects to this feature. You know, it has to do this and this as well. Well, that means that the other stuff, like our brain is finite. We can't keep it all in our heads at once anymore. But units, we can make files as long as we want. So we just, even though we're incurring a paging cost, we can slam in those additional attributes to the feature.
But it creates a blind spot for us where, you know, now that we're not thinking about the persistence, you know, bugs can creep in through that door. And if you work this way, a year passes and pretty soon your units are just gigantic and they look like this and they're riddled with bugs.
And normally, day 400 is the day that I get a phone call saying, hey, can you help improve our tests? Of course, this isn't a testing problem, right? It's like a complexity management problem. And it's hard for me when I see this cycle repeat over and over again to let that distrust kind of like throw into cynicism. And that's not good. And in fact, if I'm empathetic, I realize that at the root of this industry, all of
us really struggle to predict how complexity is going to change and to guess the complexity of stuff. And I do it too. And so what I want to do is change the question and instead focus on how to prepare myself and other people for the inevitable increase of complexity.
Because on any maintained system, almost any of them, they're going to, complexity is going to go up over time. It's just a matter of what that graph looks like. And so like Pascal, I made up Searle's wager in my head where sure, complexity might remain constant or it might go up. And yeah, we could keep writing these brain-sized units or much smaller ones. And the complexity doesn't change, you know, no harm, no foul.
It doesn't really matter how we factor our code. But if it goes up, we have a lot of evidence that these larger units cause problems. And the nice thing about small ones is that they can actually accommodate some additional complexity without too much pain. And that's why on day one of every feature, I break things up into itty bitty tiny units to the point where people criticize me.
And you know, if you think that my units are too small, don't worry because I trust that you're going to go and make them bigger later. It'll work out. It's a great way if you struggle with like trying to make, follow the single responsibility principle and have every object do one thing, this makes it really, really easy. In fact, this is what the tree of functionality kind of shook out as in this example.
And every single unit serves exactly one purpose. And they all follow one of three roles that I've kind of observed over the years of practicing this. The parent nodes are delegator objects. They don't really have any logic or branching, maybe just like one if condition or something. They mostly just break up the work and hand it off to other things.
And the way that I happen to do that is I use this test-driven design where I use test doubles to identify and think up what's the code I wish I had that would actually do the real work. What you want to try to maximize is these leaf nodes. So this is where the core logic of the application is. It takes inputs and transforms into some kind of output.
You know, these are pure functions, so you don't need test doubles. You just are actually testing real logic. These are the kinds of unit tests that everyone likes to write. And it's sort of like functional programming for people who don't want to think too hard about functional programming. It makes it very accessible because you can still do it in Ruby. You don't have to change languages or something.
On the left are my values. And value objects, they just wrap a little bit of data. Maybe they just hold onto a hash or an array. And the methods on them are only allowed to elucidate the data and answer questions about the data as opposed to doing feature work. They're not there to actually build features. Instead, I think of them as the sludge that flows through the pipes of my feature code.
They're the types in those method signatures. And even if all this abstraction doesn't ultimately pay off, and even if that feature doesn't change a lot in the future, in the very worst case, I've got a very discoverable system of carefully named things that are obvious and small and comprehensible. So it's not the worst outcome in the world.
The third aspect here is that I distrust myself even more than all of you. It's because I worry about the future. And even though I'm not super confident in today me's skills, I'm even more worried that my future self won't be able to program either for some reason. And so when I'm writing a feature, somebody says build this thing, I'll think about how to build it,
and then I'll build kind of a message in a bottle for myself in the form of better tests and documentation and commit messages, things to try to help. And my future self, who's wearing sunglasses, is told to change that feature. He gets really frustrated because what those tests mean is now he's got a bunch of other stuff to do every time he wants to make a change,
and my future self would much rather just start fresh and be able to write new code. And so I kept trying to do myself favors by writing a lot of extra tests and adding in all this quality at the beginning, but it would actually tie my hands. And so I had to reflect, how do I bake quality into my applications without creating an undue burden for my future self?
And so I started working a little bit differently. Because if this is our tree of functionality and our manager comes in and says new requirement, like thanks to some HR fiasco, all handshakes have to happen between three people, the traditional way to solve this is to carefully read the existing code, add, remove, change test, change the code,
and then try to make as small of a mess as possible. And I say small mess because any time the purpose of a piece of code has been two things, whenever we change a unit, it kind of carries with it technical debt. It confuses the story of why it existed because it's changed over time.
So instead I've been trying to make my code disposable. What that means is that when I look at this tree, I search for all of the affected units by the change. These are two units that are going to change. And so then I find the smallest subtree that encapsulates that change. And so there it is right there. And then I do something unusual. I blow it all up.
I just knock it out. Because I trust that my future self is going to be able to see that top level thing and understand what the contract is. So there he is. He's going to drive out a new solution. And as opposed to just changing these old units that implemented the logic the old way and thus rack up technical debt, I trust him to drive out a new solution.
And in fact, future us is going to have more experience than present day us. They'll probably have a better understanding of the business. They'll be able to write better units in the future than we ever could hope to today. So this is a healthy way to work. It does mean that I try not to reuse code too much in my future code because any code reuse, like if you have a method that's called in nine places,
it's really hard to change that method because you have to consider nine different place callers and what they need. It's also really hard to throw it away and replace it. So I try not to reuse too much code. And it also forced me to let go of this idea that maintainable code has to live forever. In fact, as opposed to that, this incremental rewrite in the small as part of your process is a way to pay technical debt
without saving a rainy day fund for when you finally get to refactor. And in doing that, I actually made myself happier. Because future me does not want his job to be fixing all of Justin's old janky tests every time that he changes something.
He wants to be able to write code for a living. And so this process by making death a part of life while we're working through features is a great way to keep your teams happy, I found. The last bucket here is economical versus thorough. The question one, better to ship code quickly than wait until everything is tested. Eh, I feel like I'd just be bailing out water in that case.
Two, design principles are useful, but most teams waste too much time on them. It's possible, for sure, but few teams are at risk of this. Three, most teams lack a sufficient understanding of their dependencies. Absolutely. 90% of us have no clue how most of our code works in our applications.
And four, it's okay for everyone on a team to maintain separate coding styles. I actually strongly disagree, because this leads to siloed development. It's kind of like a Conway's law of style. So does that make me economical or thorough? Well, of course, it makes me thorough. Some of you are noticing that this spells salt.
That's because when you make up the quiz, you can make it spell whatever you want. It's going to surprise a few of you to learn that I have a confession to make. I'm a bit of a control freak, and that means I'm dubious of free stuff. So when you see a sign that says free puppies, all I read is extra work. And another thing in our industry that makes me think extra work is open source.
And so just keep that in your head. Next time you're on GitHub asking for free labor from an open source maintainer, imagine you're yelling at a puppy instead, because who yells at puppies? You jerk.
Open source isn't free, because just like puppies, we have to learn how to use it. We have to learn how it's changing and follow it, and it's not just this panacea that we can pull off the internet instead of having to build stuff ourselves. And if that open source has a bug, in theory, sure, we can fix it ourselves, but in practice, we're not going to understand all that stuff.
We're probably going to have to rely on a maintainer or an expert to come and fix it later. And finally, let's say that this is our graph of objects, and it's all very consistent everywhere, and we slam in some third-party API. That's going to create a certain amount of friction and pain, because it's not going to look like all of our other objects.
In fact, third-party dependencies have this really nasty habit of leaking references all over our code base, which makes it really hard to change or replace or upgrade those third-party dependencies over time. So my temptation of all these negative things about open source and puppies is to, well, I don't have a puppy, first of all, again, much to my wife Becky's consternation,
but two, I try to avoid using open source. That's what my gut tells me to do, but that's increasingly untenable these days, because I'd be reinventing wheels constantly. So I can change how I thought about open source instead to maybe protecting myself from its blast radius, as opposed to avoiding it entirely.
The way that I do that is I write wrappers of all the third-party code that I write. In fact, we already have one up on this tree. It's that finds hands unit on the left. So this is a wrapper object, and it starts as just a well-named delegator. It's a no-op. It doesn't do anything interesting. But it comes to encode all of our understanding of that dependency. It is the carpet under which we sweep all of the stuff that we have to do
to make that third-party dependency happy. And I integration test it, but only in proportion to how suspicious I am of it breaking. Otherwise, I trust that it itself is tested well. And it might feel like needless indirection, but I need you to trust me that it's not. Of course, this is where recalling finds hands in our top-level code, and I just told you not to trust other developers,
so you don't trust me. You assume that that wrapper is unnecessary, let's say. And instead of using that call, we're just gonna call user.all instead. That way, it's much more direct. We got rid of that needless indirection, which means we have to update our test. So this is our original test. We can get rid of that fake finder and instead have an array of real users
that we create in the database. Of course, validation failed, so now I have to add a birthday of some incidental other arbitrary thing to make this work. But I can also get rid of the stubbing here and pass real users into the other stubbing. And I gotta make an integration test as opposed to a unit test. And ask yourself now, what did we just do?
Because the value of this test used to be crystal clear. Its purpose was to break the work up into four clear responsibilities. And now what is it? It's calling through the database, so it's making sure that the thing works, but only when three-quarters of the code paths are fake. That doesn't seem right. That seems wrong to me. And it really becomes obviously wrong
when this becomes more complex. Because if we add a couple of scopes, like only search for full-time and active employees, then our test isn't enough anymore because those scopes are like branches. So now we need two or three test cases to cover everything. And now it's abundantly obvious that we sliced things wrong. So when I hear teams complain
you have too many abstractions or too many objects, typically I think it's not that you're making too many abstractions or that no abstraction is somehow better. It's that you're probably mixing the levels of abstraction in your system because life with wrappers is much easier. If I look at that same thing and dive into what that wrapper looks like, yes, on day one it looks like needless indirection,
but on day two when it gets more complex, it's easy to write a test for this. There's a place for that complexity to go. And over time it might become a little bit more complicated as you distance yourself further here with a transformer to a type that you own as opposed to an active record user.
So let's say our boss comes in and says we're gonna change from active record to the SQL gem. Traditionally, this would really panic everyone in the room because you're gonna have a billion references to active record all over your system. But when you're writing wrappers carefully on your third-party dependencies, you're actually preparing for yourself an adapter interface that's minimal
and specifies exactly how you use that dependency. So you can even answer questions like would it be possible to switch to this third-party thing? And if you did, you can create an alternate implementation and run both in parallel for a while. It's a much, much better way to work. And what I found is it's a way to maintain, still have all that convenience of open source and sucking in these useful libraries
while maintaining control over how you work. So when you're as introspective about this stuff as I am, you run the risk of explaining the universe through yourself. In fact, if you ask anyone who's ever worked on a project with me what my favorite way to write code is, it's my way. And I'll fight you for it.
So my inclination is to just work really hard to convince you all that my way is best. And I'd love to say that that's an altruistic thing and I want you all to be better programmers, but it's not. It's selfish. What it really is is I'm afraid I'm gonna have a manager someday tell me that I can't write code my favorite way anymore. And when I reflect on that,
even if I were to come up with the perfect way to write code and hand it to you, I run the risk of robbing you of that same autonomy, and so that wouldn't be good either. And so that's why I'm getting out of the silver bullet business. So even though I talked a lot today about how I program, I'm not here to sell you on that. I mean, I hope you find some of it useful, but what I really am here to sell you on
is the idea of pausing and introspecting about how you act, feel, and think while you're programming so that it becomes more explicable to you and then you can articulate it to other people and improve and share. And you might ask yourself, hey, if we all start thought-leading ourselves, won't that just create chaos on our teams? And I think that's actually a valid concern,
so let's spend a minute to talk about that. Because earlier I said, if you lock down all those arbitrary decisions up front, you can spend more time being productive. That's true individually, but it's also true as teams, and that's why when you have disagreements with other people on teams, you should aggressively pull those forward because the earlier you have that discussion and say, okay, we'll use these semicolons
or we'll follow this style, if you can agree to that stuff early and lock it in, you'll, as a team, be more productive. And in fact, if you look at the average team, remember what I said earlier, we're all just imitating other programmers. So most teams actually like the idea of normalizing. In fact, some of them like it too much, where if you have a creative idea of doing things differently,
I don't want to rock the boat, I don't want to do my own thing off here or go off the reservation, quote unquote. So instead, we tend to just gravitate towards the lowest common denominator on a lot of teams, and that's not good. But that's not you anymore, right? Because after today, you're going to be thinking about how the actions you take could be improved,
your feelings, improving your thought processes. And on your next team, when you join a team of all enlightened Hugh Jackmans, some of whom will be salt and some of whom will be fine, which is literally what the other four traits spell. And some will be sail and some will be fault. When you look at this team, yeah, sure,
they all have very strongly held opinions of different ways of doing things, but the one thing they have in common is when they're approached with a new idea, they have a system for testing it and figuring out whether to adopt or reject it. And so they're not afraid of trying new things. And that's why I think that really introspective developers, when they're on a team,
they might just look like they're talking at the whiteboard all day, but they can actually have a multiplicative impact on each other as they grow. And that's really my dream today, is that if we can, as an industry, normalize the concept of metacognition and self-improvement so that we can explain how to program, this whole place might start making a whole lot more sense.
And that is how to program. So I appreciate your time here today. If you're looking for a company to work with that's gonna support you on this kind of journey, I hope you'd consider checking us out. That's our page explaining what it's like to work with us at Test Double.
And if you're a company that is looking for additional developers on your team, I hope you'd consider working with us. We join other teams just as additional developers with an interest in helping everyone get better as we go. And you can learn more about us on our site. By the way, I really made this quiz. It's a real thing. I actually printed out like 100 copies,
and I have it in my bag, so you can get a special commemorative edition today if you come up and say hi to me, I'll hand you one. I also have a lot of Test Double stickers. If you don't want to say hi to me in person, you can actually go to testdouble.com slash salt and fill it out. We made a Google form, and then two of our agents yesterday actually figured out how to automate Google forms,
so it'll calculate your programmer type and email you right away, so I hope you'll check that out too. But most importantly of all, I'm really thankful for this opportunity and for your time this morning. Thank you.