Articles > Write better code — Outside in

Write better code — Outside in

Written by
Holden Rehg
Posted on
September 22, 2018

“Software is a great combination between artistry and engineering.” — Bill Gates

Top Down and Bottom Up

Phrases like “top-down” or “bottom-up” can mean a lot. Maybe you’re thinking about how projects are divided into milestones before milestones are defined as sets of tasks, how your brain processes information, or defining a hierarchical organization in a company.

I’ve never been able to fully wrap my head around the idea of software being built either up or down.

Some programmers use these terms to describe the processes for designing and building software. Building bottom-up consists of developing building blocks that stack and rearrange to shape out the rest of the system.

I’ve never been able to fully wrap my head around the idea of software being built either up or down. Maybe you can think about building a program as constructing a building where the foundation is laid, then a frame or structure goes up, etc. But software doesn’t really play by the same rules. Going along with the building analogy, when writing software I might build a bridge between two buildings before those two buildings exist. It’s hard to conceptualize a floating bridge connecting two non-existent buildings. A lot of things can take your program down but gravity is pretty low on the list.

How About Free Floating Components?

I end up thinking about software as these components floating in free space that can handle IO and contain multiple sub-components within them.

Thinking about it that way, it’s more about designing software “inside out” or “outside in”.

After going through school and working in software, I haven’t heard these terms thrown around too much. Inside out seems to be assumed across the board. You hear a lot about writing testable code, reusable functions, single responsibility components, writing tests before the code (TDD/BDD), breaking large problems into small pieces (Algorithmic Thinking), etc.

Those are some great ways to approach code especially if you have plenty of time to do all of the planning, pseudocode, and pen and paper system designing that you need to do. But realistically, you just run into situations where you need to be more efficient than that. And ideally not write horrible code in the process that is just going to be refactored later.

Defining Outside In

Try scrapping the idea of writing units first. No tests first, no functional methods, no inner workings of your program. It’s so easy to focus on the hardest problem first which means you jump right into trying to implement an algorithm or define complicated logic.

Instead, we will contradict that idea and do the following:

  • Define the entry point for the code.
  • Assume that any module, function, helper, library, etc. is available, even if the code does not exist.
  • Write the ideal version of the entry point.
  • Write the ideal version of any code that was assumed until you start reaching “low level” functions that perform the bulk of the logic.
  • Repeat until you have all functions defined.
  • Now actually implement the logic for the “low level” functions.

Coding is a combination of creativity and common engineering practices. There are thousands of ways to approach a problem and still come up with the same answer. Outside in development is a way to think about software development from a high level so that we can work through problems efficiently on the fly while still producing good, readable, testable code.

An Example

We are going to look at building the start of a forecasting system that takes a set of project management tasks and builds out a schedule for the assigned users.

  • This is on a web application where users will log in to see their tasks and schedule.
  • It will be connected to a persistent database.
  • Assume every employee works 8 hours a day M-F
  • Assume every employee takes off federal holidays in the US
  • Add 2 days of padding for each timeline entry
  • Use a task data structure that consists of a name, deadline, assigned user, project reference, and estimate hours.

The Entry Point

With working on units first we might have started with building out a class and a few functions like this. In this case, we would be jumping into the nitty gritty of the scheduling functions.

# time.py

class DT(object):
    def get_us_holidays(self):
        pass

    def get_weekdays(self):
        pass

    def set_back_days(self, days):
        pass

    def set_back_weekdays(self, weekdays):
        pass
        

Instead, let’s try finding our entry point for the system and working outside in. With this being a web application, we know users are going to going to load a certain view to see their schedule. Let’s say they are going to be doing that through some sort of controller:

class ScheduleController(Controller):

@get('/my/schedule')
def show_schedule(self):
    """
    Shows a set of forecast items for all of the tasks the
    current user has defined.
    """
        

In this case, maybe we actually have a framework behind the scenes where we can access Controller and @get or maybe we don’t. Either way, we want to write this in the simplest, most ideal scenario possible even if the code doesn’t exist. We’ll deal with the details later.

Now we move on to writing the ideal version of show_schedule .

from app import http, auth

@http.auth()
@get('/my/schedule')
def show_schedule(self):
    """
    Shows a set of forecast items for all of the tasks the
    current user has defined.

    Assume that the schedule has already been generated by
    the task object throughout their lifecycle.
    """
    current_user = auth.current_user()

    return http.response(
        view='my.schedule',
        data={'tasks': current_user.tasks}
    )
        

We are making even more assumptions here:

  • Assume that we have http and auth available.
  • Assume that http.auth is an annotation available.
  • Assume that http.response is a function available.
  • Assume that auth.current_user is a function available.
  • Assume that the response function handles view based on a string and accepts a dictionary of data.
  • Assume that the user getting returned from current_user has a property called tasks available.

These are all details that we could have dove into immediately or even start coding within our show_schedule function just to get them off of our mind. But if you can make the assumptions and not worry about the details until later then you’re not prohibited and can just focus on writing the elegant solution.

Digging Deeper

Once the entry point is coded out then you just start funneling deeper into the program (i.e. outside in). Let’s start accounting for functions we know do not exist.

# app/http.py

from app import views

def response(view, data=None):
    """
    Send an http response back to the client.

    :param view: str
    :param data: None|dict
    """
    return views.render(name=view, data=data)

def auth(fn):
    """
    @http.auth() annotation to prevent users
    from access a certain view in our app.
    """
    def annotation():
        pass

    return annotation
        
# app/views.py

def render(name=None, data=None, content=None):
    if not name and not content:
        raise Exception('Pass either a file path through 'name' or file contents through 'content')

    if name:
        # Process a view from a file path
        pass

    elif content:
        # Process a view from the file contents
        pass
        

You can see how we just keep recursing down further into the program.

  1. Create the app/http.py module and make sure we have the functions that we need available for show_schedule.
  2. Write them in an ideal way.
  3. Since we need a way to render views, create our app/views.py and add the render function. This is the point where we are getting pretty low level and the bulk of the view rendering logic would probably live. That’s up to you to decide as the programmer of course.

We could do it again with the auth.py helper:

# app/auth.py

from app import session

def current_user():
    if not session.active():
        raise Exception('There is no active session.')
    return session.active().user
        
# app/session.py

import psycopg2

def active():
    """
    Check if there is an active session.
    """
    return _get_current_session()

def _get_current_session():
    return psycopg2.execute('SELECT * FROM sessions WHERE active=true;')
        
  1. Create the app/auth.py module and make sure we have the functions that we need available for show_schedule.
  2. Write them in an ideal way.
  3. Since we need a way to render views, create our app/session.py and add the active function. This is another point where a lot of our “low level” logic could live. Similar to the views.py we created.

Conclusion

I’m not going to write out the entire program here since it could get pretty lengthy if we assume there is no framework but hopefully, that gives you an idea on how to approach code outside in.

It’s a different way to think about things and not perfect for every scenario, but a great tool to add to your skill set.

Thanks For Reading

I appreciate you taking the time to read any of my articles. I hope it has helped you out in some way. If you're looking for more ramblings, take a look at theentire catalog of articles I've written. Give me a follow on Twitter or Github to see what else I've got going on. Feel free to reach out if you want to talk!

software craftsmanship
refactoring
web development
python
Share:

Holden Rehg, Author

Posted September 22, 2018