Programming

Programming

Procedural Programming: Back to the Future

Updated: 1/17/2022

This story is a "I'm done with OOP" story.

At my day job, we have built a REST API framework in C# that we use to publish enterprise web services. The framework was designed using OO design principles and strict separation of duties, with the goal to maximize re-usability. This resulted in many "layers" in the framework, each layer becoming its own NuGet package. This resulted in a complex web of packages, where packages depend on packages which depend on packages, and caused the following problems:

  • Difficult to find the code that's doing the work, so that you can fix it.
  • Difficult to debug, if the offending code is many layers deep in the package hierarchy.
  • Package versioning issues.
  • A fix in one package may "cascade" a whole series of other package updates. Some simple, one-line fixes can result in 4 Git PRs and package deployments.
  • The original goal - re-using code - never materialized, so we were paying the cost of maintaining all these packages but nobody (literally nobody) was using them other than us.
  • Unintentionally locked ourselves out of our own code. In many cases, a method was marked private in a package several layers deep. Sure, we could go back and mark it public, but then we'd face the package cascade problem. Oftentimes we wrote a lot of goofy code to work around this problem.

That said, for all its faults, the framework is solid and provides good value. So when it was time to upgrade it to "2.0", I wanted to change the rules a bit. Here's how I got there.

A friend of mine is an FP (functional programming) enthusiast and advocate, and is internationally known in the Elixir world. I hadn't toyed much with FP since college, so when I ran into him, I started toying with various FP languages, including Elixir and Haskell.

I started taking some of the principles of FP to my day-job OO programming. The big change was a simple one: favor static methods in static classes over instance methods. This simple change meant that my functions increasingly lacked side-effects (they didn't change the state of variables in the object, or outside their scope).

If you step back and take a look at this, you'll see that what we've really done is take C# and turn it back into something closer to C (well, mostly anyway).

We applied this principle to our latest overhaul of our OO-designed product, and it has helped increase re-usability of our functions, increased testability, and has reduced bugs overall.

Here are our new "procedural programming in OO languages" principles I've adopted:

  • Functions should be static, and reside in static classes.
  • Static classes are there to contain functions (ala modules in procedural programming); do not use inheritance on them.
  • Functions should be public unless absolutely necessary.
  • Functions should have no side effects on the containing class (they can mutate an incoming class, but try to avoid that too and instead return a new, mutated instance of the class instead).
  • Use high-order functions when reasonable.
  • Use classes and objects only for passing data around to the functions (and favor the new data classes for this).
  • Make functions null-safe. The best way to do this is use high-order collection functions (LINQ in C#, Streams in Java) whenever you're working with collections of things (which is a lot for most business programming), and implement default behavior for nulls, or empty collections. This too is borrowed in a way from FP (pipelines and high-order functions like map, filter, zip, etc.)

Last note: Kotlin is great for implementing these concepts. If you're in Java land, you owe it to yourself to check Kotlin out.

Advent of Code 2019 Day 1 in Kotlin

Posted: 12/24/21

This is an older Advent of Code solution, but the idea here was to share my solutions to some simple Advent of Code puzzles in different languages.

I like to code in Kotlin because it gives me so much flexibility: I can code in procedural, object oriented or functional styles, and I can even mix-n-match them. This isn't unique to Kotlin, for sure, but Kotlin also adds:

  • Simple syntax "sugar" (lots less typing and more coding!)
  • Public by default. Ever lock yourself out of making a function call IN YOUR OWN CODE!? So annoying! So I have a new rule for my team: unless you MUST hide a function, don't. Public everything unless you absolutely have to.)
  • Fully interoperable (bi-directionally) with Java: Kotlin classes and functions can be used in Java, and Java classes in Kotlin.
  • Null pointer safety. Oh yes please!
  • Java-to-Kotlin code converter
  • Compiles to bytecode
Kotlin has helped me be a better developer, too. Just try re-designing one of your APIs in Kotlin, and force yourself to use the null-safety everywhere. No cheating. Trust me, you'll have to re-think your API a bit, but when you're done, it'll be a better API and it'll be null safe.

Enough about Kotlin, let's see the code! If you are interested in the problem, you can read it here.


import java.io.File

fun main() {
    println(File("data/Day1.txt")
        .readLines()
        .map { s -> Integer.parseInt(s) }
        .map { x -> (x / 3) - 2 }
        .sum());
}
              

Notice that no "container" object was necessary: you can put a main function in a file and boom, go. The only "hard part" of running a small Kotlin project like this is dealing with the compilation and project dependencies, and for that you should get the Intellij IDE (more on that below).

In this example I use a function chain. First I create a new File() object. Note that I don't have to use the "new" syntax to get a new object: I just call the constructor like any other function, and I get an object back. Also note that the File() class is coming from the Java SDK, not from Kotlin. So I get to use Kotlin syntax with Java objects here.

After the object is created, I call the File.readLines() function, which returns a collection of strings (lines read from the input file). I then use the map() function to convert each line to an int, and then pipe that to another map function, where I do a little math on each integer (integer divide by 3 and subtract 2). I then pipe that list of ints to the sum function, and print the result to the screen.

If you're interested in exploring Kotlin, I suggest you just get the IntelliJ Community Edition from the JetBrains website. IntelliJ is excellent, and since I started using it, I wouldn't do Java or Kotlin without it.

You can get help on using the IntelliJ IDE, and with Kotlin, in all the usual places. It takes a little getting used to, but once you've got the hang of it, you won't want to go back.

How to Upload your Website to Neocities from GitHub Actions

Posted: 12/22/21

This site is hosted on Neocities, but I keep the sources in a GitHub repository. I keep it simple: I edit the code locally, check it in, and push to the main branch. A GitHub Action then kicks off and uploads the site to Neocities.

Neocities provides a simple REST API for uploading files. I use a community action script (bcomnes/deploy-to-neocities) to do the actual uploads.

Here are some important things I've learned using deploy-to-neocities:

  • It blindly uploads EVERYTHING in the target directory. The first time I ran it, it tried to upload my hidden directories (.git, .github) and all the crap in them.
  • If any one file in the fileset fails to upload, the script fails. When this happens, it goes into an infinite loop.
  • Neocities blocks certain file extensions. In my case, it was rejecting .map files. Because it rejected the .map files in my css directory, the script failed to upload anything and went into an infinite loop.

To work around these issues:

  • I first create a temporary directory which will contain all the files I want to upload.
  • I then copy the files explicitly, leaving out the offending .map files, and skipping the hidden directories.
  • Then I use that as the target directory for the deploy-to-neocities script.

Here is my action script in full:


name: Deploy to neocities

# only run on changes to master
on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: create output directories
      run: mkdir publish publish/js publish/css

    - name: copy files
      run: cp -R *.html fonts images publish

    - name: copy javascript
      run: cp -R js/*.js publish/js

    - name: copy css
      run: cp -R css/*.css publish/css

    # When the dist_dir is ready, deploy it to neocities
    - name: Deploy to neocities
      uses: bcomnes/deploy-to-neocities@v1
      with:
        api_token: ${{ secrets.NEOCITIES_API_TOKEN }}
        cleanup: false
        dist_dir: publish