Cosmic horror, code quality and language design

Following the last post on using iteration for improving software, I'll demonstrate how to improve code from the first version of the simple orbits model.

First though, a little detour through how you might think about better code, with a brief stopover in the land of programming language design.

Through a terrible veil

I'll warn you up front, as soon as you start down the path of discussing how-code-should-be-written you push at a strange and terrible veil between worlds. Think Lovecraft, the Chaos of 40k, the Upside Down of Stranger Things.

Science and programming are beautiful and there's so much to gain and learn from being involved in either... but you need to steel yourself when you go out there to learn the programming part. You'll find yourself transfixed by the bright lights of madness if you venture out too long, or too earnestly.

Puritanical

In brief, because software is in everything and everywhere there are thousands of different ideas, languages, business models, objectives and experiences as it concerns programming, most of which is felt through the need for livelihood.

This ultimately infuses a zealotry into any discussion of how to approach programming, and a often one motivated by concerns far removed from your own circumstances. It's very difficult as a learner not to get lost in this cacophanous roar when starting out.

I think the best you can do is attempt to develop the ability to reason about your appoach and tools, and be willing to flex when circumstances change. Really try to do the simplest thing you can until you can reason through a better approach. Don't know, be able to think it through and explain the rationale to yourself and others.

Remember always that programming is a tool for solving a problem, not an end in itself.

Write code to read

For now, I'll focus on some wisdom you'll come across often enough in software: That code should be written to be read. This is a great starting point, because it's relevant even in the case of a single person writing a program as much as it is to a large project, and the benefits will manifest immediately.

Far more time is spent reading programs than writing them. That includes you reading your own code, during development, extension or bug fixing. Time is money, and so anything you can do to make it easier, faster and more productive to read your code, the better.

Humans: High level languages

In fact, coming late to the software party, it's easy to lose track of the fact that a huge amount of effort has gone into making programming more accessible to humans. This has been done the same way it's done everywhere: Abstract away the details required to get the computer to do something.

A programming language like Python is a high-level language, which means a few things.

For our purposes, you can think of it as an approach to creating a language that prioritizes the human interface to the computer. That is, make it easier for a human to write logical instructions for the computer, without that human having to worry about the details.

There are tradeoffs, but this type of language is successful and ubiquitous.

Interface and abstraction

Think of the interface in a modern car. A steering wheel, brakes, gas pedal, gear shifter. Every time the driver touches one of these things, the car satisfies that input by doing a lot of things automatically.

Hydraulics, driver assist, antilock brakes, traction control and fuel management systems all allow the car to achieve the intent of the driver, without the driver having to think about how those things are done, or having to provide direct commands.

This is how a high level programming language works. You provide instructions in a language a human is familiar with, such as x = 1 + 3.

This means you want the comptuer to add 1 and 3, and store it in variable x. The language will take care of the details of communicating that to the computer, and otherwise adding whatever is required to make that happen.

Many details
A lot of things you'll probably never need to know about are required to add 1 and 3 in a modern computer system.

Whether a direct design goal or not, a benefit of these languages is that if it's easier for one human to write and reason in, it's also easier for another human reading the same code to understand the intent of the original author.

Write better code for yourself
That human may be a version of you six months down the road that doesn't remember why you did what you did and can only understand by reading your own code. Don't let yourself down.

Why this detour into language design?

The point here is that while yes, you need to provide instructions to the computer to get a result, civilization has put a lot of effort into making programming easier and more productive for humans, mostly because human time is expensive. Monetarily, and also in the few trips we get around our local star.

If we could do better, say, by having a system automatically generate programs from spoken language, we would. But we can't (do this reliably).

Until that time, don't waste everyone's effort. Take the excellent languages we have today and go further. Write programs in a way that makes it as easy as possible for a human to understand and use your programs!

The method validates the result
This is especially important in scientific, analytical or research softare in that these programs are more often "white box." The way they work matters a lot more than other types of software, and it's even more likely that someone will have to read and understand the code at a deep level.

Improving code

Now some practice.

Recall the basic function for calculating a planet's x and y position on a grid.


  import math

  secondsInADay = 86400

  def get_planet_xy(radius, days, angularVelocity):
    
    x = radius * math.cos(angularVelocity * days * secondsInADay)
    y = radius * math.sin(angularVelocity * days * secondsInADay)
    return x, y
            

In the line x = radius * math.cos(angularVelocity * days * secondsInADay) a number of things are being done to get the x value:

  1. Days are being converted to seconds, as our angular velocity is measured in radians per second
  2. Days measured in seconds are being multiplied by the angular velocity to find the elapsed angle of the planet in its orbit
  3. The cosine function and radius (distance from the Sun to the planet) are being used to find the x value of the planet on a grid

The computer couldn't care less, these instructions are fine. However, it wouldn't be especially clear to a human who isn't already familiar with the approach to understand what's going on.

Algorithms and recipes: Step by step

Breaking this algorithm up into parts and naming each with a variable will make it clearer for a human reader, and won't make any difference to the computer (mostly...). I've turned the original line into two extra parts:

  1. Unit conversion. daysInSeconds = days * secondsInADay,
  2. Angle calculation. orbitalAngle = angularVelocity * daysInSeconds

  import math

  secondsInADay = 86400

  def get_planet_xy(radius, angularVelocity, days):

    daysInSeconds = days * secondsInADay
    orbitalAngle = angularVelocity * daysInSeconds
    
    x = radius * math.cos(orbitalAngle)
    y = radius * math.sin(orbitalAngle)
    return x, y
            

Using variable names as a form of documentation is a standard technique, and while easy to let slip, you should pay close attention to ensure the names are accurate and up-to-date in your own code, for readability!

Using descriptive names

I think the variable names could be a little more descriptive. There are all kinds of conventions we could use but for now, updating variable names like daysInSeconds to orbitalDayInSeconds makes it a bit more specific to the problem.

It also makes explicit that we're working with the concept of an orbitalDay: A value for the elapsed number of days a planet has been moving in its orbit.


  import math

  secondsInADay = 86400

  def get_planet_xy(radius, angularVelocityPerSecond, orbitalDay):

    orbitalDayInSeconds = orbitalDay * secondsInADay
    orbitalAngle = angularVelocityPerSecond * orbitalDayInSeconds
    
    x = radius * math.cos(orbitalAngle)
    y = radius * math.sin(orbitalAngle)
    return x, y
            

Quality code

Okay, that's a little better although this should not be taken as an example of ideal code. The definition of quality code is highly dependent on your context, and so you need to develop a way to reason about what it looks like for your own specific problem.

There's a lot to know

That was a lot of context loading to do something so simple, but it's important to your ability to reason through your coding challenges to have a mental model of why you might choose a particular approach, and it's important to see this kind of thing in practice.

Next up, exploring the idea of launch windows using the simple orbital model.