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

Recipes for reducing cognitive load

00:00

Formal Metadata

Title
Recipes for reducing cognitive load
Subtitle
yet another idiomatic Go talk
Title of Series
Number of Parts
542
Author
Contributors
License
CC Attribution 2.0 Belgium:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Being the maintainer of a fairly active oss project (MetalLB) over the past year, I reviewed a substantial amount of contributions. During this process, I identified a set of recurring idioms and patterns that less experienced contributors keep missing, making the codebase harder to read and to maintain. In this talk I will describe what cognitive load is and why it matters, and provide a way to reduce it via a set of quick and easy recipes. Using this set of actionable recipes the audience will be able to drastically improve the quality of their Go code with relatively low effort.
CognitionWorld Wide Web ConsortiumExpert systemRouter (computing)Inheritance (object-oriented programming)Internet service providerFeedbackStructural loadCognitionProjective planeSoftware maintenanceFood energyTraffic reportingSoftware bugRoundness (object)Complex (psychology)Goodness of fitTerm (mathematics)FrequencyCodePhysical systemWordMultiplication signMereologySoftwareComputing platformPattern languageCryptographyWorkloadArithmetic meanFunctional (mathematics)Software developerComputer animation
RoboticsSource codeCASE <Informatik>Exception handlingComputer animation
Food energyFunctional (mathematics)Revision controlObject (grammar)CodeImplementationSummierbarkeitComputer animation
Normed vector spaceMaxima and minimaError messageCodeOrder (biology)FlagMultiplication signRule of inferenceLine (geometry)Food energyException handlingThumbnailSet (mathematics)Functional (mathematics)Error messageBlogComputer animation
Data miningBookmark (World Wide Web)CASE <Informatik>MereologySpacetimeObject (grammar)BlogDirected graphTouchscreenFunctional (mathematics)Computer animation
Error messageGamma functionError messageObject (grammar)Interface (computing)Type theoryWrapper (data mining)Software developerFunctional (mathematics)Category of beingString (computer science)Multiplication signInstance (computer science)Computer animation
Function (mathematics)Parameter (computer programming)Multiplication signParameter (computer programming)outputPhysical systemState of matterFluid staticsFunction (mathematics)Category of beingVariable (mathematics)Functional (mathematics)Set (mathematics)
Gamma functionDependent and independent variablesRevision controlObject (grammar)State of matterCASE <Informatik>Variable (mathematics)Software testingIntegrated development environmentComputer programmingReading (process)Software bugMereologyFunctional (mathematics)Game controllerTraffic reportingLogicParameter (computer programming)Client (computing)ImplementationStack (abstract data type)Physical systemCodeComputer animation
Boolean algebraComputer-generated imageryOperator overloadingParameter (computer programming)Boolean algebraPointer (computer programming)Computer fileData structureUtility softwareWordVariety (linguistics)Functional (mathematics)Service (economics)Software developerFrictionWebsiteSoftware testingRevision controlInstance (computer science)Game controllerOrder (biology)Computer configurationFront and back endsSoftware maintenanceGenerating functionInformation overloadComputer animation
ThumbnailFunctional (mathematics)Object (grammar)Rule of inferenceBitPointer (computer programming)Order (biology)Computer fileComputer programmingWechselseitiger AusschlussPoint (geometry)Exception handlingJSONComputer animation
CodeComputer fileGreatest elementImplementation
Computer fileObject (grammar)Utility softwareFood energyOrder (biology)NavigationFunctional (mathematics)LogicHeegaard splittingField (computer science)CodeComputer animation
Functional (mathematics)Hand fanGroup actionOrder (biology)DataflowCodeClient (computing)WikiSynchronizationLogicRight angleCoroutineWeightMereologyMultiplication signWebsiteComputer animation
Functional (mathematics)Multiplication signCodeAnnihilator (ring theory)ResultantMultiplicationFood energyOrder (biology)Dependent and independent variablesCASE <Informatik>Sinc functionSoftware developerComputer animation
Electronic mailing listCodeBitLecture/Conference
Set (mathematics)CodeRule of inferencePareto distribution
Functional (mathematics)FlagException handlingBoolean algebraCASE <Informatik>ImplementationRule of inferenceLecture/Conference
Hash functionEmailComputer animationProgram flowchart
Transcript: English(auto-generated)
Okay, our first actual speaker today is Frederico who is a maintainer of metal LB, which I personally use. Thank you
Over at Red Hat and he'll be talking to us about cognitive loads. So a round of applause for Frederico Yeah, it works Yeah, so today I'm gonna talk about cognitive load or and how
it affects our code base why it matters and how we can reduce it and Over the past I would say two years. I started Contributing first and then maintaining the metal with project is anyone using it
Okay, so if it's gotten less stable that's because of me But by by doing that I started reviewing a good amount of PRS and over this period I Kind of identify the recurring patterns that I was keeping asking and asking over
Those are occurring patterns those scattered suggestions That I try to give in code reviews are what this talk is about in terms of code Metal LB is a nicely sized project not too big not too small and I think it's worth
Keeping alive So some quick words about me and Federico. I work for Red Hat I'm part of a networking team in charge of making the OpenShift platform suitable for telco workloads That means that I touch and contribute a lot of these different network related projects But that doesn't mean that I'm a network expert because I'm not
So don't come asking to fix your router as my parents do because I want All of these are my handles probably the master one Needs to be adjusted but you can find me there if you ask questions to ask if you need to provide feedback
Whatever. I'll try to reply So let's start with cognitive load and this is the Wikipedia definition cognitive load is meant to be the extra energy the amount of effort that we need to put in place to understand something and That these that applies Perfectly to our code base
It might be because we are reading something that we wrote is years ago where we were less expert it might be because we are trying to review some code that is somebody else is trying to To push to our project it might be because we got a bug report and we need to correlate the behavior that we get from from the reality and
what we understand from our from our code and The less energy we spend we are able to spend the better because it might be evening and we might be tired and we might Have some some urgency about that. So that's why it's so important and sometimes this complexity
Is proportional this extra energy is proportional to the complexity of our code think about cryptography think about ultra optimized Code that runs in the embedded systems But some other times it's not take this example and takes take the same run through an obfuscator this
Take takes a lot more energy to understand that this function prints Hello world. So this is to say that We need to put an effort because that effort gets our reward in terms of speed of development and speed of understanding
So say that a disclaimer not everything is black and white Of course, there might be exceptions to the suggestions that I'm gonna say and this talk is more or less a collection of scattered the robots that I Collected from sources that I trust so in case you don't like it. You don't like them blame the sources and
In General I think that the stuff that we write should take care of two sides one is of course the implementation and This implementation is pretty clear, I guess
This function is just doing the sum of two numbers. It's easy to understand we can't argue with that, but what if we land on a code base that is doing something like this and This takes more energy Compared to a better version of it Where then the function is namely not is named nicely so we understand what was it then
This is to say and this is something that is going to be recurrent in this talk That what matters is not only how we care about the implementation But also how we care about the users of our packages of our functions of our objects
So let's start for with the with the first item which is the line of sight And this is something that I believe every good and idiomatic you go code base should try to foster basically, we have these left leftmost indented line where all the happy path leaves and We have this indented one where we handle all the exceptions and they expect every code base
Which is well written where I want to to respect this rule and there are a few tips to do this It wasn't me So These are just tips to do to do that to implement this and let's see why it matters
How it will make our code base better This was more or less a real example that I got from a real PR And it was really hard to follow all the special cases And so I tried to give feedback and try to hammer hammer it to with suggestions in order to leverage
early returns and Flipping errors Removing else is when I see an else is something that I try to get rid of like it's a red flag and I think three times before allowing it to go through and then leverage more returns and then
Yeah leverage more returns and then Sorry, yeah wrapping in into a function so we can leverage even more returns because now we have we have a Smaller scope so we got to something from something which looked like this
to something that looked like this and I dare you to say that this is easier to understand and Remember like this is understandable, but these require select energy. It's clear. It's It's better because of all the reasons that I already said before There is this nice blog post from my trier
About this very same topic he more or less gives the same set of advices Line of sight is not a nice exercise It's a rule of thumb that allow us to untangle our code and to make it Slicker and easy to easier to understand
Next I'm gonna talk about package names this another favorite of mine we know that naming is hard and That is particularly true in case of package names we know that a The name of a package should be small enough because it that is consuming screen space
But should be also good enough to let us understand the purpose of the package But in go there is even more because when we use an object the name of the package is part of the name So that is an opportunity for us to put some value in that part that the reader can
Can consume and again, I'm starting with a bad example We have this util package and we have this copy node function. That is totally fictional but That util part is a wasted opportunity. It's part of the name that doesn't add any value
So it's better to take and split our package is smaller scoped packages That do and and explain what to do and in this case From the calling side you have no copy which still explains the the purpose of the function and it's not wasting space and this was taken from the
Official go blog and it says basically the same thing. There is no need to have these gigantic kitchen sink packages where we throw everything Because in go packages are free, so it's fine to split them in in in in a better way
Next one is going to be about errors and I see also this happening very frequently Ingo errors are types and let's say that the developer wants to handle a special error and The problem with this approach is that we are giving away the fact that errors are types and
we are converting them to a string and we are treating them as a string and Since ago 1.13 we have like and there are like that's legacy So there are no excuses not to use this There are two ways one is to assert that the error that we are checking is an instance of a given object that we have somewhere and
There is another Sorry, this is new because the other one wasn't working. So And there is another one which is about asserting that the error that we want to handle implements the the error interface against a specific real type
But there is more so in in this way you can have Wraps or of errors and you can assert that the error that you are checking Not only equals the one that you are handling but also Any error inside of this wrap and this is how you've wrapped them. You can either use errors that wrap
So the return function of the the return error from this function will contain the value return by these but will also Return true if we assert against the wrapper for a rapid one and also there is the the way suggested by the
Standard library, which is using the percentage W for matters. So both of them will return you a rapid rapid error so now let's talk about Pure functions and why they are important
So a pure function has two properties one is the fact that no matter how time When you call it no matter how many times you call it with the given with a given set of input parameters it will return always the same output and the other property is the fact that it shouldn't rely on
The state of your system. Shall it be sorry? It shouldn't modify the state of the system shall it be global variables or static variables or your input parameters or Anything that is external to the function and why it matters This is an example where this function the behavior of this function depends on the on the state on of an external system
That is accessed through a client and then you have the business logic after that and why this is bad I would say that mostly because this is hard to test or We can we can mock the external system. We can do tricks to replace the client, but
moving away the Stateless statefulness part of the function away and having the business logic implemented. Sorry implemented as Pure function will allow us to be quicker in writing the implementation and to write our tests and how about the second part?
So we have a function that that accepts a pointer and in in some random cases it change it changes the object and What's the problem with that the problem with that is now on the reading side because you don't know that It's not clear enough that this function is changing the node So you you get your bug report and and you look at the code and you know that
somewhere that the name of the node changed, but you don't know why and that's because That's because it's not clear from outside that is what this function is doing and it's harder to reason about it. So
a better way Is to change the name of the function so it's clear but I think that and the This comes quite often a better way to do that is to Delegate the responsibility of changing the object outside and changing the function to be a pure function again This version is easier to understand. It's easier to reason about
it's clear when when you will have something to change and These can also say it about environment variables in the world of pods and containers I Think a new knob as an environment variable is so convenient
You just add the environment variable you consume it from the function where you need it and you're and you're done but the problem with that is that you you then Don't have control anymore on all the knobs on all the parameters that your program is access is consuming because they are all scattered across the code base and that is bad because you can't
For a see what a given function A given function is doing by reading. It's It's calling site So again, this is something that should be avoided Environment variables should be read in the in your main in your main functions and then be propagated
Through through all the all the stacks So another topic that I care about is function arguments and The first one is
Booleans So You start with something like this Where you have a simple setup function that is easy enough and then with all the good intentions of the word Thanks With all the good intentions of the word the the developer starts adding a parameter
but then we need another one and then we need another one and How does it look on the colon side something like this and you think? hmm true false true true false what the hell and Then you need to stop you need to enter into this function. You need to understand was it where was the
Enable webhook parameter. Oh, well, it was the first one and then you get back here and this Works but adds friction and Getting a better version of it is so Cheap that we should do that because we are doing a favor to our future selves
We are doing a favor to the maintainer and it's gonna gonna be easier to understand another option might be to pass a structure to the to the function that also works, but not this Now
I want to talk about Function overloading or the fact that go doesn't have so it's more or less the same as the other one Go doesn't have function overloading. So it's easy to have these Full variety of Of the same function where we need to slightly change the behavior So you start with creating a service then you need one for with a back end
Then you need one with an IP and then you need one with the back end and we not with an IP and It's clear that can get easily out of hand. So an approach that I That I really like is using a variadic argument with some modifiers that then that accept the parameter and do what?
what they have to do and This is how it looks from the colon site again, it's clear it's easy to understand you'll Your future self will thank you for this
And there is also another version where you can have these Generator functions. I I think it's on the borderline of being too magic for me, but Again, this one is easier easy to read So the next one
I See this happening a lot in in the world of controllers where you have one file that Basically implements or all the methods related to a controller So you have this file and you need to add a utility function and then all the other functions are methods And what do you do you are the new method method even if it doesn't have to be a method
So you look at something like this and you think? hmm, why is this a method this is is there something wrong with that and These again is adding friction that could be avoided so if a function is a function just make it a function and not a method because it's
Also testing is easier You don't have to have the instance that you are not using for for anything just in order to test this function and then a word about pointers Go has pointers like or Not all other languages. So people might
find them hard to reason about and When I see two functions like this My first thing thought is like this one is not changing the object that the second one is doing is doing that So this is the rule of thumb that I'm trying to apply if a function is not changing the object then
Pass the object value. Otherwise pass the pointer But there are also exceptions there are some kind of objects that can be passed by value or they can but they will give you a bad afternoon but so Mutex is file descriptors. You need we need to pass them by by reference because that's the way it works
So we have linters that help us in that and we have this rule of thumb that says if you look at the point at the objective all the All the methods associated are associated with a pointer then use a pointer One might argue. How about performances? We are passing the whole object instead of passing just the reference
Yeah passing the reference is cheaper, but this is not see this is go and that's not always clear So what we should care about is the readability and we have a lot of toolery that will help us to understand if that That can be optimized if it's in the in the
hot path and then we need to sacrifice a bit the Relevability of our program in order to have better performances So now I'm going to talk about something that was advocated in clean code where it says that
Our code code base should read like a newspaper Which means that you open a file you should have all the high-level concepts on the top of the file and then Start to find all the meaty details of it the implementation in the bottom of the file and these applies pretty well
To go so what I expect from a good a well written go file is to have all the public methods all the Public objects in the top of the file because when I open the file I see what this package has to offer to the external world And so you you those are our high-level concept concepts by definition
And another thing that I think it's all sometimes Underestimated is the fact that we can have our packages split into files So again in order to have a better navigability of our code base We can split it into files have a
main File related to the package that is named after the package and then having these Smaller entities where we put the the different logics and this is basically What I'm trying to say here, so try to have the public fields on the top
Try to remove or to move the util functions in the bottom Split the package into file because again, it's free. It won't cost any any energy to you or or to the to the executable And have a main package of file that is named after after the package
next item is About asynchronous functions and I saw these Many times in It's one of the nice things about go right it's also easy so convenient to implement concurrent code
You can just implement go routines. You can pass channels and have fan in fan out but but the problem with that is that something like this is has some flows and I think that is way better to again take the business logic move it to a
Synchronous function that is easier to test without all the infrastructure that you need to put in place with With channels with the weight groups in order to have your to to reverse the synchronous Synchronousness of your function just in order to test it So if you can move the business logic into a synchronous function and let the calling site
Handle the lifecycle of that go routine. So again that part it has to be delegated on the client code and that will make our function easier to test and our code base easier to reason about and Again, I didn't invent this as everything else
This is from the code review go wiki and it's basically saying the same thing like Try to use a synchronous Synchronous functions as much as you can Oh Next item is about functions that lie and
What I mean by that you have something that is what would you expect this function to do? Clear the node exactly. That's what I would expect but The developer found a very edgy corner case where if the name of the node is do not clean
Then do not clean and he was doing that with the all good faith of the world He's trying to solve a problem here but the problem is that again this is going to give us a bad afternoon because we'll see that the node is not being cleared and we'll have to put a lot of
Printfs in our code or to do a lot of debugging in order to understand Why is this happening? So Again this is done with good intentions, but the result is not so good. So Again
As I said multiple times today we should defer their this responsibility to the call inside Because that will result in a better in a in a code base that requires less energy and less effort To understand what if we have this function called a hundred times in our code base, then I don't know just call it
Clear the node but not the do not clean one or have one filter function whatever but not lie to the reader so Wrapping up
There is no much to wrap up. I mean it was just a list of no related items Maybe the only takeaway that is global is to say that We should be smart and let our readers The the call inside over the code base do a bit more
Because that will give us a better a better day in the in the future I'm a strong believer of the pareto paratus principle Most often when it's on the on the bedside of it But in this case, I think that by applying this set of rules that will take very less to implement those will implement in
improve the quality of the code base a lot and Then I want to finish with This quote from Rob Pike Simplicity is complicated, but the clarity is worth the fight and with that
I'm finished Yeah Are there any questions? I'll try to come with a microphone if it doesn't work. We'll have to repeat it
Hi, thanks for the talk Was wondering do you see any room for automating some of these like rules and Wisdom that you share today, maybe something else as well. I
Don't know. I should think about that probably some of them. Yes Like avoiding having functions that or raising a flag if a function is accepting a channel for example But there there are exceptions to that so That shouldn't be blocking it. There are some others like the the function that is lying to the user
is something that depends on the on the implementation or for example having a Function that accepts five booleans should be flagged So I see that I think that it depends on the case but some of them might be automated
Any more questions, no, thank you very much. How was it?