avatarLuís Soares

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

7071

Abstract

p id="b38c">If you want to write a function, you should first write all the steps needed to achieve the function goal without knowing how they will be implemented. Those steps are merely calls to non-existent functions with the inputs and outputs in place.</p><div id="d10d"><pre><span class="hljs-comment">// the function below was writen without any of the referenced functions</span> <span class="hljs-comment">// (when done, you can ask your code editor to generate the needed functions)</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { customers := readCustomersCSV(<span class="hljs-string">"input_data.csv"</span>) custumers30YearlsOld := filterCustomers(customers, <span class="hljs-string">"age"</span>, <span class="hljs-number">30</span>) customersWithDiscount := activateDiscount(custumers30YearlsOld) writeCustomersCSV(<span class="hljs-string">"output_result.csv"</span>, customersWithDiscount) } <span class="hljs-comment">// you should apply this mindset to any function (not just main)</span></pre></div><p id="3b94"><i>Notice that the steps in the example are written almost in plain language. It works like an outline or an index when you read it at a glance. As in a recipe, you can tell the whole story from there (besides, all the steps present the <a href="http://principles-wiki.net/principles:single_level_of_abstraction">same level of abstraction</a>). It’s <a href="https://readmedium.com/towards-self-documenting-code-371364bdccbb">self-documenting code</a>.</i></p><p id="73c7">With steps-first, you’re forcing yourself to define function signatures before implementation, driven by their intended usage, like an upfront plan on a tiny scale. Then you apply this reasoning recursively (top-down approach), starting with the entry point (e.g., <code>main</code>), creating functions (at the same level of abstraction) and doing the same in each of them until the problem is solved. Besides, you should try to properly name a variable before knowing how it will be computed rather than the opposite, which forces you to define the step purpose first.</p><p id="3cfc">Steps-first is a way to tackle inherent complexity because you’re splitting a problem into smaller ones (“dividing to conquer”) — the functions you’ll create later.</p><h2 id="8a2b">Begin with the function goal</h2><p id="3215">What is coding backward? It’s a way to write functions where you start with the function goal — its return or side effect (<a href="https://martinfowler.com/bliki/CommandQuerySeparation.html">depending on whether it's a query or a command</a>). Write the function starting at its end (that’s what’s known from the start) in the last line of code, and work your way up (into the unknown). Per line of code, you must ask, “<i>What do I need just above to make this feasible?</i>”. Do this until there’s nothing else to do.</p><div id="419d"><pre><span class="hljs-comment">// Iteration 1️⃣ </span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// step 1: what do I want to achieve? </span> <span class="hljs-comment">// to write a CSV of 30yo customers with discount</span> <span class="hljs-comment">// now I need to compute that variable...</span> writeCustomersCSV(<span class="hljs-string">"output_result.csv"</span>, customersWithDiscount) }

<span class="hljs-comment">// Iteration 2️⃣</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// step 2: now I need custumers30YearlsOld...</span> customersWithDiscount := activateDiscount(custumers30YearlsOld) writeCustomersCSV(<span class="hljs-string">"output_result.csv"</span>, customersWithDiscount) }

<span class="hljs-comment">// Iteration N</span> <span class="hljs-comment">// ... (till you're done)</span></pre></div><p id="a5c9">You can apply this technique to any function, especially higher-level ones like <code>main</code>.</p> <figure id="7675"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=twitter&amp;url=https%3A//twitter.com/KentBeck/status/1497278365836795908&amp;image=https%3A//i.embed.ly/1/image%3Furl%3Dhttps%253A%252F%252Fabs.twimg.com%252Ferrors%252Flogo46x38.png%26key%3Da19fcc184b9711e1b4764040d3dc5c07" allowfullscreen="" frameborder="0" height="281" width="500"> </div> </div> </figure></iframe></div></div></figure><p id="717d">Coding backward generates leaner code because at each step, you do solely what needs to be done and no more. Each line of code is driven by the one below.</p><h2 id="e91c">Begin with the assertion</h2><p id="d874">A special example of starting a function by its end is when writing a test, where <a href="https://twitter.com/KentBeck/status/1497279829858652161">you should start with the assertion</a> and move your way up. This means doing the <a href="http://wiki.c2.com/?ArrangeActAssert">Arrange/Act/Assert</a> (the same as <a href="https://martinfowler.com/bliki/GivenWhenThen.html"><i>Given/When/Then</i></a>) in the inverse order. That forces you to have the minimum code possible in that test. <a href="https://readmedium.com/anti-patterns-of-automated-software-testing-b396283a4cb6">A test case should isolate a certain scenario as much as possible</a>, and starting with the assertion — which represents the goal and the reason for the test — is the best way to ensure it.</p><div id="4069"><pre><span class="hljs-comment"># Iteration 1️⃣</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_calculate_rectangle_area</span>(): <span class="hljs-keyword">assert</span> rectangle_area == <span class="hljs-number">12</span> <span class="hljs-comment"># there's an error because rectangle_area in not defined</span>

<span class="hljs-comment"># Iteration 2️⃣</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">test_calculate_rectangle_area</span>(): rectangle_area = calculate_area(<span class="hljs-string">'rectangle'</span>, <span class="hljs-number">4</span>, <span class="hljs-number">3</span>)

<span class="hljs-keyword">assert</span> rectangle_area == <span class="hljs-number">12</span>

<span class="hljs-comment"># Iteration 3️⃣</span> <span class="hljs-comment"># auto-generate a dummy calculate_area function</span> <span class="hljs-comment"># and see the test fail</span>

<span class="hljs-comment"># Iteration 4️⃣</span> <span class="hljs-comment"># (we don't need an Arrange in this test)</span> <span class="hljs-comment"># implement calculate_area solely to pass this test</span></pre></div><b

Options

lockquote id="45e6"><p>Write the outputs, the assertions and the checks first. Then try to explain how to get to those outputs. […] When tests are written from the outputs towards the inputs and contextual information, people tend to leave out all the incidental detail.<i> <a href="https://www.goodreads.com/book/show/25564600-fifty-quick-ideas-to-improve-your-tests">Fifty Quick Ideas To Improve Your Tests</a></i></p></blockquote><h1 id="6b5e">From knowns to unknowns</h1><p id="2b69">The pattern is that we define the goal and keep it in sight. We start with intent/purpose and ask how we can get there.</p><div id="1b72"><pre>⬇️ start here Problem solved (goal) ⇢ Strategy to get there <span class="hljs-keyword">Interface</span>/comments/test ⇢ implementation <span class="hljs-keyword">Function</span> <span class="hljs-title">steps</span><span class="hljs-keyword">Function</span> <span class="hljs-title">bodies</span> End (goal) of <span class="hljs-keyword">function</span> <span class="hljs-title"></span> beginning of <span class="hljs-keyword">function</span> <span class="hljs-title">assert</span> ⇢ act/arrange</pre></div><p id="05c4"><a href="https://www.goodreads.com/book/show/36072.The_7_Habits_of_Highly_Effective_People">Begin with the end in mind</a>. You’ll work backward, from knowns to unknowns, from <i>what</i>’s<i> desired </i>to <i>how</i> to achieve it. Work your way from the former to the latter.</p><div id="52a5"><pre>⬇️ start here knowns ⇢ unknowns what ⇢ how goals ⇢ execution <span class="hljs-keyword">end</span><span class="hljs-keyword">implementation</span> idea ⇢ strategy problem ⇢ solution</pre></div><p id="5580">Start with the <i>what</i>; only then worry about the <i>how</i>. Distinguishing them ensures your mental power is fully applied to each. It brings clarity to your thoughts. It ensures alignment on a goal before jumping hands-on. We could call it purpose-driven development.</p><blockquote id="d28d"><p>We always take care to separate the problem and solution. The solution doesn’t matter if the problem isn’t worth solving. <a href="https://www.goodreads.com/book/show/50776459-shape-up"><i>Shape Up</i></a></p></blockquote><h1 id="3873">A guiding light</h1><p id="85b4">Having no defined goal can be tiring, confusing, and demotivating. For example:</p><ul><li>We often observe early solutionizing, like designing UIs upfront before any research. Some people go as far as starting by defining database schemas. We must think about the goal before jumping too fast to write code. We spend too much time discussing technical details, but we should first agree on what is being solved.</li><li>Autonomous teams should be trusted to fulfill a high-level goal. How they get there is their concern; otherwise, you’re micromanaging.</li><li>Many meetings would be shorter and more productive if people started by setting up the desired outcomes.</li><li>Technical articles would benefit from a proper intro with the <i>why</i> (e.g., a summary of the problem and solution).</li></ul><p id="f1c6">Defining the goal and keeping it in sight would solve all that. If you have a dream or a life goal, you set a goal and picture yourself in it; devise ways to get there; keep the goal in mind as a beacon of light; follow a strategy and keep reassessing if you succeeded.</p><figure id="763e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*KMxq5o-ay5qdjmzo.gif"><figcaption><a href="https://en.wikipedia.org/wiki/Polaris">Polaris</a> makes an excellent fixed point from which to draw measurements for celestial navigation.</figcaption></figure><p id="b6ec">Besides, assigning purpose to what we do is one of the pillars of motivation. Keeping the goal in sight provides focus and direction. It commits your efforts to a goal. You can always answer why you’re doing something.</p><blockquote id="0fab"><p>There is a great deal of evidence that people are hardwired to care about purposes. […] There is also much evidence that people suffer when they lack purpose. <a href="https://www.goodreads.com/book/show/2407072.Intrinsic_Motivation_at_Work"><i>Intrinsic Motivation at Work</i></a></p></blockquote><h1 id="3bac">Keep it lean</h1><p id="ef1b">One of the <a href="https://readmedium.com/towards-lean-software-development-24460340b11a">lean development tenets is to maximize value while minimizing waste</a>. I hypothesize that the <i>end-in-sight</i> mindset is the main weapon against waste. It keeps <a href="https://en.wikipedia.org/wiki/Overengineering">overengineering</a> in check because, for everything you do, you can ask if it’s in line with the set goal. If you ask, “<i>Why am I doing this?</i>” you can trace it back to the <a href="https://en.wikipedia.org/wiki/Five_whys">root cause</a>. Just ask <i>why</i> a few times and check if you were led to <a href="https://www.interaction-design.org/literature/topics/user-centered-design">something with user value</a> (I do it when I’m feeling lost). If you don’t know the goal, ask why or don’t do it. To put it bluntly, don’t do anything without an end in sight.</p><blockquote id="9894"><p>To get something done, you have to start with some notion of what is wanted — the goal that is to be achieved. Then, you have to move yourself or manipulate someone or something. Finally, you check to see that your goal was made. <a href="https://www.goodreads.com/book/show/840.The_Design_of_Everyday_Things"><i>The Design of Everyday Things</i></a></p></blockquote><p id="f17a">This approach will forcefully discard everything that looks like “<i>Let’s do it for the future</i>”, “<i>We gotta try this new tech</i>”, “<i>Let’s create a generic solution</i>” or any other diversion from the set goal (regardless of its size). Keeping the destination clear ensures you never forget why you’re doing something. The golden rule is that you can only pick the actions that lead you there with less effort and discard the rest. It’s easier to focus and cut short diversions from the goal. This applies to everything we do, from product strategy to writing functions.</p><blockquote id="9462"><p>Purpose is what makes work energizing and engaging. People need more than a list of tasks. <a href="https://www.goodreads.com/book/show/194338.Lean_Software_Development"><i>Lean Software Development</i></a></p></blockquote><h1 id="d88d">Learn more</h1><ul><li><b>Part I —<a href="https://readmedium.com/towards-lean-software-development-24460340b11a"> The Lean Developer</a></b></li><li><a href="https://www.productplan.com/glossary/working-backward-amazon-method/">Working Backward (the Amazon Method)</a></li><li><a href="https://reproof.app/blog/document-first-then-build">Write documentation first. Then build.</a></li><li><a href="https://www.sitepoint.com/comment-driven-development/">Comment-Driven Development</a></li><li><a href="https://tom.preston-werner.com/2010/08/23/readme-driven-development.html">Readme driven development</a></li></ul></article></body>

Working backward

After becoming frustrated with overengineering, I adopted an approach to prevent it: setting a goal and working backward.

Photo by Jametlene Reskp on Unsplash

What is working backward?

Working backward is setting goals and asking what’s needed to get there. It’s working from the end to the beginning, from knowns to unknowns. I’ll describe the method with examples of various types and generalize later.

Begin with the problem solved

When you set out to solve a problem, you imagine a world where that problem is solved. Then you ask, “What do I need to make this a reality?”. You start with the known: people don’t have the problem anymore and navigate to the unknown: how to get there. This is true whether you’re creating a new product or adding abilities to an existing one. In the second case, you add the problem to the board as a user story and slice it down into smaller user stories, each incrementing value to the product. When picking up one of them, you start with the UI and move down until you solve the problem.

Beginning with the end in mind (the goal) is the most useful way to focus on the real problem and avoid discussing solutions too early. It also helps to do solely what’s needed. The opposite of this is developing technological solutions and forcing them into user needs.

Having a goal in mind doesn’t mean you need to follow a plan. It’s crucial to remain flexible and adapt to new information as you move forward. As you deliver small chunks of value, your understanding of the domain will likely change, and the world around you evolves. Therefore, it’s important not to view the goal as written in stone.

Begin with the interface

When starting the work on a user story (which is only a problem statement), it’s common to start discussing algorithms or even the database, but that’s plain wrong. Generically speaking, you should start at the top (the top is usually a UI), build the next need, and do it recursively till you reach the lower level — top-down approach. Unlike the bottom-up approach, where you build low-level components first and try to guess what’s needed above, in the top-down approach, you’re driven by needs rather than guesses.

In the top-down approach, we repeat a cycle of defining and implementing an interface (interfaces can be GUIs, CLIs, REST APIs, function/method signatures, etc.). At each level of abstraction, the interface establishes a contract you must fulfill. Only after implementing it can you move further down the tech stack. For example, in web development (with client-side rendering), design a minimal UI first, try to implement it (perhaps using dummy data), and only then think about the needed backend API and use it (repeat this cycle until the database). If you’re API-first, you may want to start with the OpenAPI definitions.

A top-down approach ensures you don’t waste time building generic overengineered components trying to guess how they’ll be used. When you journey from the user problem to the database, you build exactly what’s needed and no more, level by level. Another benefit is that it encourages ubiquitous language since actual needs drive code writing.

Begin with the test

Test-driven development (TDD) is the prime example of beginning with the end in mind and moving steadily toward the goal. It tells you to start with the test (specification) — the reason for the change, the goal. Then, you jump to the implementation. You do it iteratively, relying on the TDD cycle.

When we write a test, we imagine the perfect interface for our operation. We are telling ourselves a story about how the operation will look from the outside. Test-Driven Development

TDD offers separation between interface design decisions & implementation design decisions. TDD forces you to think about the usage (what) before the implementation (how), so you’ll hardly produce code that’s hard to use, as the tests are the first users of the code.

Begin with the comments

Some people defend a comments-first approach. It involves writing a plan as comments in plain language to explain what we aim to do before jumping into the code details. By expressing intentions upfront, we’re forced to articulate and structure our thoughts. This way, we may spare some back-and-forths and code rewrites.

def main():
  # 1: request the type of shape
  # 1: request the user the dimensions of the shape
  # 2: calculate the area of the shape
  # 3: print the calculated area
  ...

The second, and most important, benefit of writing the comments at the beginning is that it improves the system design. […] The simpler the comments, the better I feel about my design. A Philosophy of Software Design

In my case, comments-first may be useful when I need to clarify my thoughts, especially if I’m not pairing. In those cases, I convert the comments to function calls. While I appreciate forcing myself to think about the goal before delving into implementation details, I prefer TDD and a steps-first approach, as we’ll see now.

Begin with the function steps

If you want to write a function, you should first write all the steps needed to achieve the function goal without knowing how they will be implemented. Those steps are merely calls to non-existent functions with the inputs and outputs in place.

// the function below was writen without any of the referenced functions
// (when done, you can ask your code editor to generate the needed functions)
func main() {
  customers := readCustomersCSV("input_data.csv")
  custumers30YearlsOld := filterCustomers(customers, "age", 30)
  customersWithDiscount := activateDiscount(custumers30YearlsOld)
  writeCustomersCSV("output_result.csv", customersWithDiscount)
}
// you should apply this mindset to any function (not just main)

Notice that the steps in the example are written almost in plain language. It works like an outline or an index when you read it at a glance. As in a recipe, you can tell the whole story from there (besides, all the steps present the same level of abstraction). It’s self-documenting code.

With steps-first, you’re forcing yourself to define function signatures before implementation, driven by their intended usage, like an upfront plan on a tiny scale. Then you apply this reasoning recursively (top-down approach), starting with the entry point (e.g., main), creating functions (at the same level of abstraction) and doing the same in each of them until the problem is solved. Besides, you should try to properly name a variable before knowing how it will be computed rather than the opposite, which forces you to define the step purpose first.

Steps-first is a way to tackle inherent complexity because you’re splitting a problem into smaller ones (“dividing to conquer”) — the functions you’ll create later.

Begin with the function goal

What is coding backward? It’s a way to write functions where you start with the function goal — its return or side effect (depending on whether it's a query or a command). Write the function starting at its end (that’s what’s known from the start) in the last line of code, and work your way up (into the unknown). Per line of code, you must ask, “What do I need just above to make this feasible?”. Do this until there’s nothing else to do.

// Iteration 1️⃣ 
func main() {
  // step 1: what do I want to achieve? 
  // to write a CSV of 30yo customers with discount
  // now I need to compute that variable...
  writeCustomersCSV("output_result.csv", customersWithDiscount)
}

// Iteration 2️⃣
func main() {
  // step 2: now I need custumers30YearlsOld...
  customersWithDiscount := activateDiscount(custumers30YearlsOld)
  writeCustomersCSV("output_result.csv", customersWithDiscount)
}

// Iteration N
// ... (till you're done)

You can apply this technique to any function, especially higher-level ones like main.

Coding backward generates leaner code because at each step, you do solely what needs to be done and no more. Each line of code is driven by the one below.

Begin with the assertion

A special example of starting a function by its end is when writing a test, where you should start with the assertion and move your way up. This means doing the Arrange/Act/Assert (the same as Given/When/Then) in the inverse order. That forces you to have the minimum code possible in that test. A test case should isolate a certain scenario as much as possible, and starting with the assertion — which represents the goal and the reason for the test — is the best way to ensure it.

# Iteration 1️⃣
def test_calculate_rectangle_area():
    assert rectangle_area == 12
    # there's an error because rectangle_area in not defined

# Iteration 2️⃣
def test_calculate_rectangle_area():
    rectangle_area = calculate_area('rectangle', 4, 3)
  
    assert rectangle_area == 12

# Iteration 3️⃣
# auto-generate a dummy calculate_area function
# and see the test fail


# Iteration 4️⃣
# (we don't need an Arrange in this test)
# implement calculate_area solely to pass this test

Write the outputs, the assertions and the checks first. Then try to explain how to get to those outputs. […] When tests are written from the outputs towards the inputs and contextual information, people tend to leave out all the incidental detail. Fifty Quick Ideas To Improve Your Tests

From knowns to unknowns

The pattern is that we define the goal and keep it in sight. We start with intent/purpose and ask how we can get there.

⬇️ start here
Problem solved (goal)   ⇢ Strategy to get there
Interface/comments/test ⇢ implementation
Function stepsFunction bodies
End (goal) of function   beginning of function
assert                  ⇢ act/arrange

Begin with the end in mind. You’ll work backward, from knowns to unknowns, from what’s desired to how to achieve it. Work your way from the former to the latter.

⬇️ start here
knowns        ⇢  unknowns
what          ⇢  how
goals         ⇢  execution
endimplementation
idea          ⇢  strategy
problem       ⇢  solution

Start with the what; only then worry about the how. Distinguishing them ensures your mental power is fully applied to each. It brings clarity to your thoughts. It ensures alignment on a goal before jumping hands-on. We could call it purpose-driven development.

We always take care to separate the problem and solution. The solution doesn’t matter if the problem isn’t worth solving. Shape Up

A guiding light

Having no defined goal can be tiring, confusing, and demotivating. For example:

  • We often observe early solutionizing, like designing UIs upfront before any research. Some people go as far as starting by defining database schemas. We must think about the goal before jumping too fast to write code. We spend too much time discussing technical details, but we should first agree on what is being solved.
  • Autonomous teams should be trusted to fulfill a high-level goal. How they get there is their concern; otherwise, you’re micromanaging.
  • Many meetings would be shorter and more productive if people started by setting up the desired outcomes.
  • Technical articles would benefit from a proper intro with the why (e.g., a summary of the problem and solution).

Defining the goal and keeping it in sight would solve all that. If you have a dream or a life goal, you set a goal and picture yourself in it; devise ways to get there; keep the goal in mind as a beacon of light; follow a strategy and keep reassessing if you succeeded.

Polaris makes an excellent fixed point from which to draw measurements for celestial navigation.

Besides, assigning purpose to what we do is one of the pillars of motivation. Keeping the goal in sight provides focus and direction. It commits your efforts to a goal. You can always answer why you’re doing something.

There is a great deal of evidence that people are hardwired to care about purposes. […] There is also much evidence that people suffer when they lack purpose. Intrinsic Motivation at Work

Keep it lean

One of the lean development tenets is to maximize value while minimizing waste. I hypothesize that the end-in-sight mindset is the main weapon against waste. It keeps overengineering in check because, for everything you do, you can ask if it’s in line with the set goal. If you ask, “Why am I doing this?” you can trace it back to the root cause. Just ask why a few times and check if you were led to something with user value (I do it when I’m feeling lost). If you don’t know the goal, ask why or don’t do it. To put it bluntly, don’t do anything without an end in sight.

To get something done, you have to start with some notion of what is wanted — the goal that is to be achieved. Then, you have to move yourself or manipulate someone or something. Finally, you check to see that your goal was made. The Design of Everyday Things

This approach will forcefully discard everything that looks like “Let’s do it for the future”, “We gotta try this new tech”, “Let’s create a generic solution” or any other diversion from the set goal (regardless of its size). Keeping the destination clear ensures you never forget why you’re doing something. The golden rule is that you can only pick the actions that lead you there with less effort and discard the rest. It’s easier to focus and cut short diversions from the goal. This applies to everything we do, from product strategy to writing functions.

Purpose is what makes work energizing and engaging. People need more than a list of tasks. Lean Software Development

Learn more

Lean Development
Recommended from ReadMedium