Refactoring to introduce Tabs in The Console

In my weird software called The Console I decided to introduce tabs - a mechanism similiar to tabs that exist in web browsers. Here's a (not really) brief story about it and why Dijkstra was right.

chicken20or20egg20sm

Multiple contexts

Having The Console working without tabs I had previously developed some classes that are shared everywhere. Most of them are related to the instantiation of script execution. Every script is launched inside JavaScript IIFE block.

I quickly found out that simply creating multiple WebView outputs won't handle the tabs alone. For example, there was ConsoleProxy class that mimics global JavaScript variable often called in web browsers as console. Let's call console.clear() and guess what - we can't clear consoles in all tabs.

That's why I need to create so-called ConsoleContexts dynamically on tab view creation. I keep in there a few things like error stream, console input (prompt) interface or console output interface. Since scripts and commands are launched synchronously inside one tab, there's only one object for temporary arguments that are passed into executed script.

Keep 'em separated

Tabs are separated. They work in their own "sandboxes". Every command or a script that is launched in some tab should have printed it's output to the same tab. And here's the thing. Notice a simple situation:

  1. you run a script that downloads currency data from some website
  2. it takes some time
  3. you don't bother about finishing and just close tab that owns this script
  4. script finished downloading data, parses it and wants to print it into output
  5. oops, the tab doesn't exist anymore!

Software shouldn't crash. What's correct catch for this situation? Print output to nowhere? I decided to print it to Default tab. Default tab is a first tab by... default - but it's been thought to be changable.

chicken-egg problem

"Chicken or the egg" problem often relies to a situation of confusion what is/was/should come first. Remember paragraph about contexts? I bring them back!

First note that I keep logic code and view code somehow indepedently. Dependencies that need to exist are mostly referenced through interfaces (in the meaning of language construct, not the abstraction). And here's the problem - should I create the view or the logic first?

To create a tab I need:

  1. instantiate a tab that contains WebView (output) and TextField (input prompt)
  2. create a Context for this tab
  3. inject Context into tab

Sounds simple, doesn't it? Now imagine this - every software often logs information or errors, e.g. information about initialization. To show a real example, I have a ScriptManager that loads scripts on software initialization where some logs need to be presented into the console output.

So, before an initialization of "logic" I need to initialize the "view" first. Of course I can't do that because I need user settings first. And if settings loading outputs some errors (or non-error info logs) I want to present it in the console output.

My logic/view abstraction wasn't really done well since one thing needs other and the other needs the first one first. Chicken or the egg? I ended up with creating a initialization context that collects logs until WebView (text output control) initializes. When view initialization is done I simply push the entries to the real output.

Summary

Probably, to make a better abstraction would need introducing more code that handles situations of asynchronousness. I decided to KISS here.

After all, I have to agree with Edsger Dijkstra who used to say

Two or more, use a for.

Every time I see a pattern that could be multiplicated in code or runtime I experience it's often worth thinking forward about writing a code that's ready for a multiplication - in code (DRY rule) or in runtime, as here for my tabs.

My code wasn't really prepared for it, because in previous iteration of The Console I didn't really think about having tabs. Refactoring to introduce those made me feel pain.