Make Money Outside the Mac App Store book cover

My book is out: Make Money Outside the Mac App Store.

Own your products and know your customers: sell outside the Mac App Store. In a few hours, you’ll have in-app purchases, a trial mode, and piracy protection all set. The book includes fully functional sample projects and code ready to be copied into your app.

A Look at the ReSwift Event Log During Launch of My Latest Project

I use a logging middleware in all my apps that use ReSwift. I love to see the event log. It makes me think the app is healthy when it tells me what’s going on.

All this happens when I start the knowledge management app I’m working on in about half a second (reformatted a bit to make reading easier):

> ReSwiftInit()
> Routing(route: .launch)
> ChangingArchiveConfiguration(
    configuration: Optional(ArchiveConfiguration(folder=Folder(path="/Users/ctm/Pending/Zettelkasten-App/testdata"), 
    extensions=PlainTextExtensions())))
> InitializingArchive.started
TODO: show loading indicator
> Routing(route: .mainWindow(MainRoute(
    state=State(
        searchResults: ArchiveContents([], []), 
        noteText: nil, 
        showSettings: false), 
    components=Components(
        search: Search(archive=Archive(<InMemoryZettelFinder>), lastState=nil), 
        display: DisplayNote(), 
        indexing: Indexing(), 
        writing: WriteFileChanges())))
> SearchingNotes(query: Query("", context=[]))
> SearchingNotesFinished(ArchiveContents([§id2, §id], []))
> DisplayingNoteFinished.displayNone
> InitializingArchive.successful
TODO: close loading indicator
error indexing: noNote(file:///Users/ctm/Pending/Zettelkasten-App/testdata/.DS_Store)
> ReloadingResults(trigger: note changed in repository (§201201301230))
> SearchingNotesFinished(ArchiveContents([§id2, §id, §201201301230], []))
> ReloadingResults(trigger: note changed in repository (¿this is a proto zettel?))
> SearchingNotesFinished(ArchiveContents([§id2, §201202011545, §201202011557, §201202092218, §201202011633, §201202011636, §201201311533, §201202011539, §201202011536, §201202011542, §201202011602, §201202092225, §201202101433, §201202101439, §201201301230, §id, §201202011550, §201202011607, §201202011623, §201201311302, §201201311311, §201612241053], [¿this is a proto zettel?]))

The bottom part tells me the initial note indexing sends ReloadingResults events correctly. It is throttled already to fire only every 0.2 seconds, but that still means it will fire at least twice: once in the beginning (that’s the first note with the ID “201201301230”) and later when it’s done. Maybe I’ll get rid of a live-updating list of files when a lot of work is performed.

TableFlip’s log looks similar. Here’s what happens when TableFlip launches with 2 windows open. You can see that in /tmp/tableflip.log, too, if you run the app:

> ReSwiftInit()
> ReSwiftInit()
> ChangeLock(isLocked: false)
> ChangeLicenseInformationAction(
    licenseInformation: TrialLicense.LicenseInformation.registered(
        TrialLicense.License(name: xxxx, licenseCode: xxxx)))
> ChangeLock(isLocked: false)
> ReSwiftInit()
> ReSwiftInit()
> ChangeLock(isLocked: false)
> Undone with SelectTable(tableIndex: 0)
> ChangeLicenseInformationAction(
    licenseInformation: TrialLicense.LicenseInformation.registered(
        TrialLicense.License(name: xxxx, licenseCode: xxxx)))
> ChangeLock(isLocked: false)

That, in turn, tells me I can save a few cycles by optimizing the locking/unlocking of documents based on license information. Undone with SelectTable(tableIndex: 0) is an artifact of resetting the NSDocument to display the initial table data properly by making sure the first table in the document is selected. “Undone” here indicates it’s not an action that is undoable. (See my Undo middleware.)

This is what happens when you move the cursor twice to the right in TableFlip:

> selectCell(Selection(1, 1)) @ 0
> selectCell(Selection(2, 1)) @ 0
> selectCell(Selection(3, 1)) @ 0

Then type something and hit return, triggering an autosave in the process:

> changeCell((#3, #1), 'hello!') @ 0
> selectCell(Selection(3, 2)) @ 0
Determining write operation for net.daringfireball.markdown
Changed document type to canonical Optional("net.daringfireball.markdown")
Requesting data of type net.daringfireball.markdown for document of Optional(TableFlip.DocumentType.markdown)

I hate dealing with file types, by the way.

Here’s the logging middleware, in case you want to have as much fun as I:

import Foundation
import ReSwift

let formatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "HH':'mm':'ss'.'SSSS"
    return formatter
}()

func log(_ text: String) {
    let time = formatter.string(from: Date())
    print("\(time) \(text)")
}

let loggingMiddleware: Middleware = { dispatch, getState in
    return { next in
        return { action in
            log("> \(action)")
            return next(action)
        }
    }
}

How to Save File Changes Using ReSwift Actions

Let’s say you write a plain text editor that can work with multiple text files at a time. As a backbone for processing information, you use ReSwift to model the data flow in a clean fashion. The user has 10 files open, changes the text in 1 file’s tab. The user leaves the text field and your awesome autosaving is triggered.

How will saving work? How do you get the string from the text view of your user interface into a file – using ReSwift actions?

I came up with something that seems roundabout at first but makes sense the longer I think about it.

The problem in planning this: Dispatching actions in ReSwift should not trigger side-effects immediately. It’s a mechanism to change the global app state, not a notification mechanism. So there may be no direct observers for the autosave event.

I always ask myself: who might want to know about the event? Now since it’s not allowed to be notified directly, how can I model this as a part of the app’s state without getting in the way of the rest? (It may be a code smell if every event has its own app state property equivalent and you misuse the store as an event recorder.)

In the case of saving files, there may be something like a FileSavingService. It wants to know when it should do something. Autosaving should happen every now and then, but not necessarily immediately after the user enters a few characters and then stops. It can happen with a delay. (But it doesn’t need to. Immediate is just as fine.) So a queue of file changes makes sense. The app enqueues file changes and the FileSavingService processes the queue.

In short:

sketch of the data flow
The data flow, visualized
  • Dispatch a EndingEditing action with a reference to the current file and the file’s new contents.
  • Reduce EndingEditing actions to enqueue the tuple of (file, contents) (or a dedicated value type, of course) in a substate of the overall app state.
  • Make the FileSavingService a ReSwift.StoreSubscriber and process the queue – on a background thread.

I find this to be roundabout because traditionally, you want to wire ending editing to invoke a saveCurrentFile method or something. User interaction directly maps to persistence. With this approach, you introduce buffers. User interactions can happen at will and emit “please save these contents sometime” events without directly doing anything. No user interface action handler knows what is really going to happen. All they do is dispatch events. This thinking comes easier to me now than when I started with ReSwift, but it’s still surprising how this “Redux”-approach influences modeling processes in my apps.

InfiniteCanvas – Vector Drawing App Concept

I have no clue how you create drawing apps. I guess your primary concern is low latency and good-looking results rather than making the most of cool new architecture patterns like VIPER. It’s a real-time thing. Not unlike games, I imagine. Still, “in between years” (between Christmas and New Year; but I like the literal German translation), I took a day or two to come up with a conceptual implementation (MIT licensed).

I use a WACOM Intuous pen tablet (affiliate link) instead of a trackpad or mouse most days. I fell in love with the quality of WACOM tablets some 10 years ago and never want to miss one of these things again for sketching ideas and drawing diagrams. With the digital pen, I can replace most of the scribbling I do to facilitate thinking when I’m programming. I create quick sketches of class hierarchies to see where refactoring might help. I hand-draw collaboration diagrams and sequence diagrams to get a feeling for the runtime processing of data.

And now I want a slick app that can pan and zoom infinitely.

prototype screenshot
Screenshot of a working but incomplete prototype

So I set out to create it myself. Without any prior knowledge of how drawing applications work. (Hard to find a tutorial for that, too!)

My observations so far:

  • AppKit’s mouse events work well enough to draw smooth paths. While recording every point that comes in does work, drawing a NSBezierPath through all of them slows down drawing pretty quickly.
  • mouseDown means “start a new stroke”, mouseDragged means “draw path through here”, and mouseUp means “finish stroke”. Drawing a stroke is pretty simple so far.
  • Organizing the picture in terms of Strokes that are made of Points and that expose (cached!) bounding rectangles helps speed up drawing: in the canvas view’s drawRect(_:), I only draw strokes that overlap the incoming dirtyRect.
  • As an approximation, it suffices to add a point to a stroke with a minimum distance of 4pt to the last point. Ideally, the required distance would be smaller when you draw curves and larger when the line is very straight. I don’t like freehand vector drawing tools that show a precise path while you draw, then “optimize” the path so it doesn’t look at all like what you drew. (Looking at you, Inkscape!) But a clever algorithm can at least make stroke paths a bit more clever.
  • Live-interpolation of paths as you draw them works for very long and complex paths at reasonable speed, but the CPU load is way too high. Instead of storing the points the cursor passed through, smooth the path once and store the Bézier-path triple of (point, controlPointA, controlPointB). That makes SVG import/export easier, too.

So you’re very welcome to contribute to the project if you want to experiment with a simple drawing app! Once I got past the initial hurdles, all of the remaining steps seem to be very straight-forward. I created GitHub issues for most of the things that need to get done already.

Until the app is ready, I’m using a cross-platform drawing app for artists, by the way, called Mischief. It’s on the Mac App Store, too. It works pretty well for what I want to do, but it can only save the drawing as a rasterized image. Also, I don’t like locking people into data silos and proprietary file formats.

I’d like to make InfiniteCanvas SVG-based for maximum compatibility with other vector drawing software. That’s part of my general attitude that is best described as a “software-agnostic programming” approach, a monicker by my friend and Zettelkasten co-author Sascha. It means that good software should make itself irreplaceable through quality while giving the user maximum power over the data. Locking people in by design is a sign of weakness. That’s why plain text writing apps are morally superior. Same for SVG. Open formats, rule the world! ✊

Swipe Transitions and ReSwift

In a client meeting yesterday we tried to figure out how to animate scene transitions with swiping left/right when ReSwift is the single source of truth of the app state. What goes into the app state? How do you animate that? Should the % of the transition be part of the app state for some reason? (Spoiler: Nope.)

Swiping is challenging at first because this interactive transition from view controller A to B requires both to be ready for display: when you swipe, B needs to be “dragged in” visually. When you add custom navigation controls, you end up with a master view controller that contains a child view controller to display the actual table view (green box in the sketch below). These table views should be swiped in from left or right and trigger navigation changes.

In this example, the user sees data for a given day. She should navigate freely to the previous and next day with swiping and navigation buttons until the very beginning or end of time. (Or the limits of our data, whichever comes first.)

sketch of navigation flow
From the current day, you can navigate backward to the previous day, or forward to the next day

Let’s analyze implementing this.

Single-State Replacement, No Transitions

In a static world without transitions, only the “previous” and “next” buttons of the navigation bar (depicted in blue) trigger navigation changes: you tap the button, new data from the server is requested, maybe you show a loading indicator, then you replace the UITableDataSource’s contents.

Now if you use ReSwift, the currently visible collection of data is part of your app state. Keeping it simple, the table view’s cells will display text. The state looks like this:

struct AppState: ReSwift.StateType {
    var contents: [String]
}

Imagine you have actions, reducers, and whatnot in place to react to navigation changes. (This may be a challenge on its own and is a topic for another day. Hint: you are going to need a “change day” action to trigger the network request and a “replace data” action to update the contents.)

To show the latest state changes, you set up a Presenter that is a ReSwift.StoreSubscriber. The newState callback is invoked when you received data from the server and replace AppState.contents. Then this array of strings is passed to the user interface for displaying. Let’s call that method updateView(linesOfText:).

Here’s an architectural side-note: the updateView(linesOfText:) method I imagine the presenter to call should be exposed by the master view controller. This in turn can delegate down to its current child view controller that handles actual display of the table. But coupling the presenter, a service object outside your presentation layer (!), to a sub-view controller may harm you in the long run. The master view controller is the outer shell of the whole component, so it’s responsible for exposing a usable interface. The amount of internal components and delegation to them is an implementation detail other objects should not care about. (You’ll see why in a second.)

This setup is pretty simple. AppState changes flow through the Presenter which creates a view model if necessary, then passes that to its view component. As a result, the UITableView is reloaded with new data and you’re done.

That’s the most barebones approach. Before you add interactive transitions, let’s make it more responsive first. Right now, each button tap triggers a network request that puts the user’s interaction to a halt. Stop-and-go navigation isn’t very popular with the kids, so we’ll pre-fetch neighboring day’s data in the next step.

Pre-Fetching Adjacent Days’ Data

In the presentation layer, I imagine the situation to change a bit and look like this:

sketch of the component setup
Presenter creates a view model with 3 parts. The master view controller has 3 child view controllers to switch between them

The changes to the simple approach from above are:

  1. The master view controller has 3 child view controllers instead of 1. All of them are prepared and ready for being displayed.
  2. Tapping a button now does 2 things: it fires a “change day” navigation action as it did before, and it immediately puts the correct child view controller on top.
  3. To make all this possible, the Presenter assembles a ViewModel with 3 content arrays instead of 1.

The view model is still pretty simple:

struct ViewModel {
    let previousDayData: [String]
    let currentDayData: [String]
    let nextDayData: [String]
}

The master view controller accepts this in the new updateView(viewModel: ViewModel) method.

class MasterViewController: UIViewController, View {
    let previousDayViewController: ChildViewController
    let currentDayViewController: ChildViewController
    let nextDayViewController: ChildViewController
    
    // ... setup of the child view controllers etc. ...
    
    func updateView(viewModel: ViewModel) {
        // Assign each data array to its child view controller
        prepareChildViewControllers(viewModel: viewModel)
        
        // Put "current day" on top, hide the others
        resetTopmostViewController() 
    }
}

The app state has to reflect this overall change, too, so the Presenter can assemble a view model in the first place. I call this triple a “deck” of model data. In this contrived example, the model data is just as simple as the view model. Usually, real model data is more complex and uses custom types a lot more, and in the view model you resort to easy-to-display types. So although here both ViewModel and Deck are equally simple, I want to stress the point of giving your model types names that make sense, and not just re-use the ViewModel type from your outermost UI layer in your app’s very core.

struct Deck {
    let previous: [String]
    let current: [String]
    let next: [String]
}

struct AppState: ReSwift.StateType {
    var deck: Deck
}

And finally, the presenter which assembles all:

protocol View {
    func updateView(viewModel: ViewModel)
}

class Presenter: ReSwift.StoreSubscriber {
    let view: View
    
    func newState(_ state: Deck) {
        let viewModel = ViewModel(
            previousDayData: state.previous,
            currentDayData: state.current
            nextDayData: state.next)
            
        view.updateView(viewModel: viewModel)
    }
}

The effects of this change: “previous”/”next” content changes happen instantly, and while a request can take a couple of seconds, the user can already interact with the pre-fetched set of data.

Initially, resetting and replacing the currently visible child view controller and its contents will not feel right.

What’s going to happen:

  • the user taps “previous”
  • another table view with new data is displayed immediately, how delightful!
  • the user scrolls down a bit
  • (meanwhile, the request finished and the state update is triggered right now)
  • the view flickers and is reset, the table scrolled to top, displaying the same data as before; huh?!

Offering immediate transitions in the view layer and then resetting it hard from the core of the app causes problems for the user interaction. It takes a bit of an effort to make this smooth. In a nutshell, here’s what I’d do:

  • Use an array differ like Dwifft to compute the changes of the incoming viewModel.currentDayData with the stuff that’s on screen; if the data isn’t stale, no need to reload the table view. If it is stale, Dwifft will offer delta updates, which means you get animated insertion and removal table changes instead of a full reset.
  • When updateView is invoked, switch child view controller references. Before this change, previousDayViewController became visible. But there was no “previous-to-that” view controller. Resetting which one is on top now can become “exchange previousDayViewController with currentDayViewController” and then perform the content diffing. That’s probably easier than transferring control of the visible table to currentDayViewController or similar. And you will need to make currentDayViewController point to the topmost child view controller in order to make the navigation buttons work again.

Adding Swipe Gestures

I’m no expert in interactive view controller transitions. So my advice for this part is really sketchy:

  • Only your view layer is responsible to handle the swipe transition. It should not leak into your app’s state or something. It’s just a special kind of animation.
  • During the transition, nothing really happens, except the screen contents animate.
  • When the gesture and transition have completed, say “swipe from left to right” to pull in the previous day’s data, only then trigger a state update.
  • The completion of a swipe transition is 100% similar to tapping the “previous” or “next” buttons.

With the setup from the previous step, this should already be everything you need to do.

Because users expect to swipe on for a while without interruption, you may want to increase the range of your Deck and ViewModel in both directions: instead of pre-fetching 1 set of data, you may want to pre-fetch 3 or more. Or you pre-fetch 2 in both directions (2 + 1 + 2 = 5 in total) by default and change that to 4 in the direction the user is swiping (2 + 1 + 4 = 7 in total).

In the end, you’ll want to make sure that no matter how much you are pre-fetching, the request–response cycle shouldn’t take too long or the interaction will come to a halt again. In another iteration, you can refine the process and dispatch more granular state updates: first, request the new “current” day’s state and make sure the data hits your AppState quickly, then fire off another request for the immediate neighbors; then another one for farther away screens.

Conclusion

It turned out that “swiping between screens” boils down to:

  1. Screen transitions: switch between views of pre-fetched sets of data for high responsiveness.
  2. Adjust the app state to represent what your app needs, not the domain model; if you need to pre-fetch data, store the datasets as part of the state.
  3. Interactive and animated transitions are solely part of the UI layer’s responsibility and don’t affect state.

I want to postulate this: whenever you see the term “animation”, it’s just a presentation detail. It should be exclusively managed by the UI layer. If your domain model (the innermost core) knows about animation progress, you screwed up somewhere along the way. Similarly, the app state (the mediation layer between model and UI, if you will) should not know about animations, only discrete state changes.

Now prove me wrong!

Innocent Arrow Diagrams

Sketches and diagrams with arrows look very innocent, yet they mean so much for our work. But they only carry meaning if you’re honest with yourself and depict what’s really going on in the app; then they become a great tool to understand and analyze code. (Better than note-taking can ever be when it comes to object-oriented code.)

diagram

The image above tells that there are no other cross-connections between modules. Especially that there’s no circular dependency between things. If you want to understand how information from Module B reach Module A, the image tells you’ll only have to look at one spot. It tells this is a very simple setup. It helps to understand the flow or process of the app.

I’m always amazed how these simple things help during my work.

When my Magic Trackpad batteries died, I permanently switched to my corded Wacom pen tablet. Working with a pen is cumbersome in comparison to a touch input device, but I feel I got used to it after a while already. And I learned a few extra shortcuts so I don’t have to pick up the pen all the time. Now there’s a drawing app running when I work so I can switch to its empty canvas and sketch something that’s on my mind, just like the image you see above. I don’t waste as much paper on temporary drawings and sense-making diagrams anymore. I love it.

I love it so much that I think about writing my own vector-based infinite canvas drawing app over the holidays. That’d be cool.

Always Pay Attention to Implications of Technical Advice

This weekend, I browsed around on Medium and found a post about “3 ways to pass data from model to controller”. Comments indicate that people like it because it talks about the important basics: how do you pass info around? It’s probably the most essential object-oriented programming question ever. How can you couple components?

The 3 ways mentioned are:

  1. callbacks,
  2. delegation,
  3. and notifications.

These are indeed 3 different ways to couple any component. It’s important that you know about these techniques (among others) to write code.

Then it occurred to me that the post may be accidentally misleading new Swift developers, though, because the examples hardly show how to create a good model. Take the sample code for delegation:

class DataModel {
    weak var delegate: DataModelDelegate?
    func requestData() {
        // the data was received and parsed to String
        let data = Data from wherever
        delegate?.didRecieveDataUpdate(data: data)
    }
}

This is a network request with a delegate-based callback. Sure, it illustraties delegation. But it’s network controller code. It’s not model code. Do you see anything that represents an entity here? The only model-esque thing you’ll spot is data, actually. A model object is an object that essentially is something. What you see above is a service object that merely encapsulates a sequence. Apart from the delegate property, it’s stateless. It is itself hardly part of the state of the application.

In simple cases, it might not pay off to extract network request code from model code. But this will not scale. And if this example is among the first things you see as an uprising iOS or Mac developer, you’ll be mislead.

Say your code has model objects already and you want to apply the technique of delegation as outlined above. So you take this simplistic snippet, you see that you can just paste it in without any conflicts, and then you’re done. The successful application seems to be enough of a proof. But now your code got worse. Because your model object is now the network request gateway, too. It does two very important but very different things. Maybe the harm will show only in a couple of months from now – and if you maneuvered yourself into this situation with all the best intentions in the first place, you’re probably not aware of the source of the problem and won’t be able to fix it yourself.

When you read code-focused “X best ways to do Y”, figure out what kind of world-view or terms the author is (unknowingly) selling to you. Only with a critical distance can you take advice without blindly changing how you think. If you cannot find compelling reasons why the author does it the way she does, maybe she just did sloppy work and didn’t care much. You have to take responsibility for the quality of what you learn.

Authors can’t start at Genesis and explain how all of human history culminated in this super coding trick. When you write, you have to take some things for granted. The problem is: if you, the reader, don’t have your own opinion, the things you’ll see first will affect you the most. And if the things that are left unsaid are important concepts of your craft, it’s hard to notice what’s going on. You cannot collect code snippets without collecting a bit of the authors view on how to do things The Right Way&tm;. If the pieces in your collection conflict and you don’t notice it or don’t understand why, it’ll be hard to write cohesive code and apply any of these tips. Because in the end, nothing works in isolation; you, the human, are always part of the process, and your confusion will manifest in code.

For reference, here finally is the link to the post I talked about: https://medium.com/ios-os-x-development/ios-three-ways-to-pass-data-from-model-to-controller-b47cc72a4336

Lifting Into a New Type: My first “Idiomatic” RxSwift Unit Test

I dabble with RxSwift right now. Figuring out how to write tests for pure functions and reactive code, I tried to write an assertion for incoming events:

let expected: [Recorded<Event<URL?>>] = [next(0, nil), next(0, url)]
XCTAssertEqual(observer.events, expected)

Doesn’t compile because equating arrays with Optional<URL> in them won’t. In other words, [Recorded<Event<URL>>] (non-optional) would work.

I tries to figure out how to make this equatable, but then I stopped – why not lift the URL? into its own type? After all, the signal I’m observing is a path settings change. nil is allowed to signify removing the setting, like when you reset to defaults.

Lifting this into its own “either-or” type, also known as encapsulating the information in an enum, I end up with this:

enum URLChange {
    case set(URL)
    case unset
}

Making this equatable is simple. And the tests work, too. For the record, here’s the idiomatic Rx test:

func testArchiveURLChanges_RxVersion() {

    let adapter = SettingsAdapter(defaults: UserDefaults.standard)
    let url = URL(fileURLWithPath: "/a/url/", isDirectory: true)
    
    let disposeBag = DisposeBag()
    let scheduler = TestScheduler(initialClock: 0)
    let observer = scheduler.createObserver(URLChange.self)

    adapter.archiveURLChange()
        .subscribe(observer)
        .addDisposableTo(disposeBag)

    defaults.set(url, forKey: "archiveUrl")

    let expected: [Recorded<Event<URLChange>>] = [
        next(0, .unset),   // Initial value for fresh defaults
        next(0, .set(url))
    ]
    XCTAssertEqual(observer.events, expected)
}

And the traditional, asynchronous version I used before:

func testArchiveURLChanges() {

    let adapter = SettingsAdapter(defaults: UserDefaults.standard)
    let ex = expectation(description: "Reports back result")
    let url = URL(fileURLWithPath: "/a/url/", isDirectory: true)
    let disposeBag = DisposeBag()
    
    adapter.archiveURLChange()
        .subscribe(onNext: { if $0.url == url { ex.fulfill() } })
        .addDisposableTo(disposeBag)

    defaults.set(url, forKey: "archiveUrl")

    waitForExpectations(timeout: 0.2, handler: nil)
}

I’m not too happy with the verbose setup of the Rx test case. But I’m refactoring the setup into – wait for it – the XCTestCase.setUp() method.

For simple assertions, the async variant would work just as well. Recording longer sequences of events with some timing is interesting, though I didn’t need that, yet, to test-drive my operators.

It’s the Worst Time to Go Open Source Because So Many Stupid People Will See It

Junior Bontognali summed up what most people think about most other people – especially after the U.S. presidential election and Dash for iOS going Open Source: people in general are not ready for generous behavior.

Bogdan’s Dash for iOS is on GitHub now. One tweet highlighting an ugly if statement got lots of attention. One twitter user suggested cutting off Bogdan’s fingers. That’s not a real threat, of course. It’s just a stupid joke. But suddenly you’re in 4th grade again and everyone is making stupid jokes because someone pulled down someone else’s pants.

The thing is, Bogdan of Kapeli is too busy to accept pull requests that don’t add features. Maybe because testing these for regressions is pretty time consuming without and end-user benefit. Who knows. So he already turned down a pull request that would fix the aforementioned if-statement – and many others.

Dash probably isn’t ready for Open Source, either. A Swift library as simple as ReSwift took us over a month to port to Swift 3. Maintenance is time-consuming, no matter how big or small the project. Complaining is stupid and childish, but that’s the noise we have to deal with if we don’t want to be stupid and childish ourselves.

I don’t think everyone’s grown-up enough for democracy. I only hear about 3rd wave feminism and presidential elections in the U.S. from YouTube. Junior quoted Evelyn Beatrice Hall, and that applies just nicely to all the hate that’s going ‘round:

I disapprove of what you say, but I will defend to the death your right to say it.

It seems lots of people with the power to act in public space don’t want to defend anything except their own self-loathing and self-interest. (Looking at you, “safe space” discussions.) If you read my previous ramblings about morals, you will notice that I usually point out that bitching doesn’t help. It will only strengthen victim mentality. Only doing will improve the situation. That’s why I won’t stop open-sourcing code and I’m looking forward to getting feedback by amazing people. The majority of not-so-amazing people on the internet have to be filtered out. It’s worth the effort to reach the good folks.

Open Source and the Open, Distributed Mindset

We developers are familiar with the benefits of Open Source-distributed code: we can re-use the stuff other people achieved in our own applications and experiments. Giving back to “the community” feels nice, too. Most of the day-to-day benefits are centered around the free re-use. But the mentality of being part of Open Source is far more than that. I realized how precious the mindset is to me when I thought about how the gym I train at is managed.

Today at the gym I talked to a trainer about the new equipment and if they could’ve said “No” to it, or if the headquarters prescribe what’s happening. My gym is part of a large-ish chain of inexpensive gyms around Germany. HQ is prescribing everything. When during a routine inspection someone found out there was one spinning bike too many, it had to be removed. Micro-management like this makes me wonder: if the number of bikes is fixed, how do they account for differences in room dimensions? (I guess they don’t.) The number of clocks per room is dictated, too. They cannot even run a competition here without unlikely approval by HQ. This is narrow-minded, top-down micro management. It works, but it certainly adds to the feeling of detachment and coldness in the gym.

So what if someone at the gym is tired of 48pt Arial Bold signs saying “Out of order!!!” and gave the process a bit more thought? She could use a font family that matches the corporate identity, spice it up a bit and add brand colors. The gym owner will probably just like how it looks and that’s it. But what if our budding designer is sending the Word document to HQ into a central repository so others can make use of it, too? Some day, someone at HQ might replace the popular sign with a version that truly fits corporate design. Bottom-up improvement accomplished. Staff will feel more involved, too. Change is transparent and possible; it’s a mindset of contribution and involvement.

And that’s what I take for granted when I think about “Open Source.” It’s not just free re-use, although that’s an important part of it; it’s also an attitude of distributed work that goes without saying. You don’t need to manage and coordinate efforts in advance. Everyone can contribute. And thanks to GitHub (and git in general), it’s super easy to propose changes and merge improvements.

I wouldn’t want to work in an environment where real participation is impossible. A distributed, open mindset equals a transparent company. For me, that’s the only future worth bringing into existence.


→ Blog Archive