Better Code

STLab Better Code course

Outline

Introduction

Better Code is a course and book on engineering.

Definition of Engineering:

The creative application of scientific principles to design or develop structures, machines, apparatus, or manufacturing processes, or works utilizing them singly or in combination; or to construct or operate the same with full cognizance of their design; or to forecast their behavior under specific operating conditions; all as respects an intended function, economics of operation and safety to life and property.

—American Engineers’ Council for Professional Development, according to https://en.wikipedia.org/wiki/Engineering.

But that’s a bit wordy and vague. Boiling it down to its essence,

  1. It’s designing and building stuff
  2. It’s subject to real-world constraints, e.g.
    • Shipping dates
    • Available tooling
    • Hardware available for deployment
    • Laws of physics

And as engineers we have to juggle all of this and make a set of informed tradeoffs to find the best solution. Real-world constraints mean engineers will always confront trade-offs, so much so that making these trade-offs could be said to be the essence of the activity.

Engineering is making informed tradeoffs to produce the best design or artifact possible given a set of constraints.

Instructor/Author Bias

We hope this course/book is generally applicable, but of course we come from a set of experiences that will color what we think is important. We’ve attempted to be responsible for our biases. In full disclosure, you can read our backgrounds from the overleaf/here they are.

Truth

One thing we both believe is that in grappling with problems, if you apply yourself, you can discover deep platonic truths. This course/book is about finding deep truths about programming.

Better Code

This is a course/book about producing better code. To do that, we need to understand what good code is, and to do it together we need to agree on that definition. Rather than try to list these properties up front, we’re going to discover the properties of good code as we go along, by looking at real examples.

DWA: Now we have a slide with a list of some properties of good code, which we said we weren’t going to do. Which is it?

Software is Physics

What to expect

Programming Language

The principles of this course are not specific to any one programming language, Often we’ll talk about some programming idea and then talk about the best way to render that idea in a given language. The fit will not always be elegant; this is an engineering reality (tradeoffs!)

Structure of the Course
Conventions used throughout
Unscalable constructs

Alternate words for this:

[This is what we were calling “raw.” Unscalable is far from a perfect word. “Raw” connotes “unencapsulated” or “exposed” but Sean was also trying to get at a word for the property that causes us to want to encapsulate these things, which are two separate ideas. Unscalable is my approximation]

Pointers to materials
Useful tools

Algorithms

Flow

Tasks for Dave

Material to be Reviewed / Added

Sections, thoughts, etc., that don’t yet have an associated section in the outline.

Computational and efficient bases

Necessary ingredients for Scalable/Sustainable Software

If we can make this list long enough, it could be a theme threaded through the book.

Safety

We define a function or method as safe if it cannot cause undefined behavior in a single-threaded program. This is close to the traditional meaning of “type safe and memory safe.”

[Undefined behavior may manifest at an arbitrary time in the future, so if an operation X can cause later safe operations to manifest undefined behavior, X is unsafe. This reasoning allows us to classify std::move as unsafe in C++. See below for more.]

A safe operation can have preconditions, but its response to preconditions being violated must be defined or unspecified, not undefined.

C++ limitations on safety

Safety for C++ is complicated because common coding patterns, such as mutation, depend on fundamentally unsafe constructs like pointers and references. Therefore we define “C++-safety,” and a set of C++-safety rules that, if followed, make the use of “C++-safe” operations, actually safe by the definition above.

A C++-safe function or method may generally assume:

A safe operation is free to make additional guarantees, e.g. specifying well-defined behavior if some of its reference arguments are aliased.

C++ has “implementation limits,” such as stack depth, which, when exceeded, can cause undefined behavior. We have no special strategies for mitigating the risk of exceeding these limits; C++-safety depends on staying within them.

Move Semantics and Invariants

C++ has non-destructive move semantics. That was a mistake, but that’s how it is. Unfortunately, there are some types (or some type invariants) for which no safe + efficient non-destructive move operation exists—patching up an object that’s been emptied of its resources is not always easy/efficient, and weakening invariants to accomodate the empty state is bad for reasoning about the rest of the program.

One possible conclusion is that move operations (move assignment and move construction) should be regarded as unsafe, because they may leave the object with broken invariants. That would weaken the whole concept of class invariants, which are supposed to hold unconditionally, after any public operation. Invariants are powerful tools for creating simple abstractions precisely because they do not need to be stated as preconditions, which would be necessary if they could be left unsatisfied.

Instead, we can observe that move operations in code without std::move (or some equivalent cast) are only applied to rvalues: the language prevents the moved-from state from being observed except by the destructor that is about to run, and once the object is destroyed, the temporary breakage of invariants is irrelevant. Therefore, as long as the destructor is written to deal with moved-from states, we can view the fused move+destroy as being safe.

That leaves only moves from lvalues, which occur only after calls to std::move or equivalent casts. The language does not prevent a moved-from lvalue from being passed to, and observed by, a function whose correctness depends on the class invariants. As noted above, the invariant is not stated as a precondition, so such a call must be regarded as “usage according to contract.” Because the behavior of such a call could be undefined, std::move must be regarded as an unsafe operation (conveniently, just like any other cast).

Many use cases for moving from lvalues demand replacing unsafe, moved-from, lvalues with safe values. In-place destruction + construction creates problems for exception safety (the construction might throw, leaving a destroyed object), so a single operation is required, and for better or worse, C++ chose to spell this operation the same way as an ordinary assignment. Thus, in addition to the destructor, copy- and move-assignment operators must be written to deal with moved-from states. Every other public operation can depend on invariants being satisfied, but not these three.

On Readabilty

Too subjective on its own. This is verifiability, correctness is impossible without it…

Concurrency

Definition

Examples

  1. We have M work to do over N execution units (e.g. CPUs) and we want to go faster (throughput)
    • Give each CPU M/N work to do
    • Wait for all CPUs to finish
  2. We have to carry on multiple I/O “dialogues” at once, like in a web server (logical code organization) Netflix

  3. We have to service something at a given rate (e.g. UI) but we have long-running computations

Note: only the first of these demands actual parallelism.

Catalog of

Execution Context

*