Software Complexity

I’m currently reading a book by John Ousterhout, called A Philosophy of Software Design, and this book preaches one central tenant: reducing complexity. This is something near and dear to my heart, being someone that frequently works on his own projects. Complexity can kill projects and companies entirely, because the cost of engineering new software is just too high. I’ve seen it several times, and each time I’ve wanted to rail against it.

There are two symptoms of complexity that Ousterhout mentions, and I’ll dive into examples of both that I’ve found throughout my career. Those causes are what he calls change amplification and cognitive load

Change amplification means that when you need to add a new feature or make a change to a codebase, you have to change multiple areas just to achieve the desired effect. In my career, I’ve seen this happen whenever dealing with code generated by WYSIWYG applications, or in legacy codebases. The most egregious example was dealing with FlutterFlow generated code.

Low-code tools are incapable of abstraction, so what you see a lot of the time looks like this:

        return Scaffold( // Repeated in _every_ screen in the app.
          key: scaffoldKey,
          appBar: AppBar(
            backgroundColor: FlutterFlowTheme.primaryColor,
            automaticallyImplyLeading: true,
            title: Column(
        //...

It’s great that FlutterFlow is capable of generating code in Flutter, but what happens when you need to make a fundamental change to how each screen looks? What happens if you need to change the backgroundColor of only some screens, leaving all the others the same? This is why we program to an interface, and use abstraction whenever possible. Having a single Screen class that contains default values, solves this issue. Here’s what that might look like:

// screen.dart
class Screen extends StatelessWidget {
    Color appBarColor;
    //...

    // Default constructor and value
    Screen() {
        appBarColor = FlutterFlowTheme.primaryColor;
    }

    // Named constructor that takes an appBarColor
    Screen.withColor({this.appBarColor});

    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                backgroundColor: appBarColor,
                //...
            )
            //...
        )
        //...
    }
}

// random_screen.dart
import 'screen.dart';

class ThisScreen extends StatelessWidget {
    Widget build(BuildContext context) {
        return Screen.withColor(appBarColor: Colors.white)
    } 
}

This is a simplified example, but already you can see the benefits. Having a central location for our common Screen logic allows us to have our actual screens focus on what they want to present. It is omitted for brevity, but the Screen class would likely take a Widget input for body content, and display that as the body of the underlying Scaffold. With one common place to control our screen display, we now can make changes to all of our app screens in one place. I can change the default color in the no-arg constructor to be something else, and this change will be reflected in all screens.

Cognitive load is how much information you have to hold in your head as a developer to make a change. This low-code tool generates Flutter code that is very high in cognitive load. An example is anything dealing with the database. When working with Firebase records, and displaying that data through Flutter Flow, one ends up with many layers of nested StreamBuilder widgets. These widgets in Flutter can be hard to understand, as they add an extra layer of complexity through the concept of an AsyncSnapshot. The StreamBuilder will read from it’s stream, then call the builder function with an AysncSnapshot. You then have to coax out of the snapshot the actual data values and finally write the presentation logic to display your data.

Here’s a picture from a project I’m working on. This is the widget tree generated by FlutterFlow.

Image

The screen itself is responsible for showing what users you follow. To accomplish this, there are multiple StreamBuilder widgets for what should be a simple database query/call. Some of this is a result of Firebase, but also splitting this information up into Streams makes for a complicated mess when untangling the streams into the presentation layer. Finally, look at all of the UI-related widgets :).

The suggested fix here would be to carefully craft the query for what data one needs, and ensure that there are no nested StreamBuilder instances. This leads to having to hold less information in your head at a time when working on this screen.

Thanks for reading!

 Share!

 
I run WindleWare! Feel free to reach out!

Subscribe for Exclusive Updates

* indicates required