Lots of books, studies, talks, think groups, consultants and more have tried to figure out why software as a business is hard...but they seem all try to look at it from a single viewpoint.
I'm going to give you the secret to why writing software as a business is difficult. Hard. Nigh-on impossible.
Why? Perspective. Multiple points of view (PoV).
I've attempted to show you why in this chart. Value from one's point of view tops out at 10 here. The units are arbitrary.
The business folks start this chart on the left. The absolute best thing they could get is an app that does everything their heart desires and get it /right now/. The longer the project drags on, the farther to the right and down the blue line goes. Why can't developers ever deliver anything?
Either the market opportunity will dry up because a competitor did release an imperfect but on-time project, the sales won't materialise, or the project will run out of money before delivering.
Note that the blue line continues below zero. A below zero value is a very real prospect...it means that the project is losing money for the business.
The developers enter this from the right. There's almost zero value in giving something to the business on the first day. We've not had time to scope, research, analyse, plan, divide into user stories, write tests for, fail, refactor, meet, discuss, and eventually deliver some software. Why do the business types always demand software before it's ready?
If the business demands it at the beginning it will have a near-zero value. As time goes on, the value delivered (red line) slowly ascends. At the far right, we've hit the perfect software: small, easy to maintain, well documented, maybe multi-platform and a joy to behold...and entirely too late.
By the time we've gotten that far our company has gone out of business.
The real value that a software project delivers is represented by the green line. The maximum value is the MINIMUM of either the red or blue lines.
The best we can hope for is to find the optimal mix: 'enough' software to scratch a business need, delivered fast enough to capitalise on the opportunity.
Admittedly not perfect software, and clearly not delivered on day 1.
There are ways to try to manage both lines to enlarge or prolong the sweet spot.
You could throw more money at a project (whether that shows up as hardware, software, people, facilities, resources, campaigns, publicity, influencers, whatever...at the end of the day, it's all money) to give you a longer time period before the blue line starts to descend. However, the farther to the right you go, the less effect throwing money at a project will have.
You can try reuse (internally with code or externally with libraries), or better tech, hardware or people to move the red line to the left.
No matter how passionately I argue that we *must* refactor code or that the project must deliver by Tuesday (even if the software isn't ready), I'm not going to be helping the business.
At the end of the day, we need to swap our points of view.
If a dev looks at this from the pov of the biz guy, they'll be thinking how to move their bar to the left. Descope, suggest alternatives, innovate to do things in quicker ways.
If a business guy uses the pov of a developer, he'll see that the quick win never really existed because it wasn't obtainable in the first place, and trying for it might have actually cost us the opportunity that really was obtainable, even if it were smaller.
This is the challenge that makes software difficult, and yet keeps things so painfully interesting.
Sometimes the most important thing is to tilt your head and look at the problem differently.
I usually end up feeling foolish when this happens. The only thing to do is to own it and move on, lesson learned.
I work on a big app in my day job. As delivered, it's currently 45MB.
As you can imagine, some size could be sliced off if we could use /this/ technique, or /that/ procedure. Sadly real life doesn't seem to work that way. Apps, like people, seem to be the sum total of all the events (and corresponding scars) made over their lifetimes. There are /reasons/ why we can't use app bundling, or other methods to slim down like some other apps might.
So, when I was tasked with creating a carousel effect, though my first thought was to use the 'Android-CoverFlow' library from https://github.com/crosswall/Android-Coverflow, I balked because of the size.
I don't have anything against the code or the library, we just needed the lightest implementation possible.
So I started trying to build the coverflow effect by hand.
I found that the ViewPager is distinctly odd in the Android world. If you luck into the right point of view, it's a no brainer.
If you, like me, make invalid assumptions, it's a nightmare of code smells: machine-specific code, hooking into the global layout of the widget, strange unexplained offsets being required, etc.
So, instead of being very long winded, I'm going to show you a git repo.
One branch, "BadCoverflow", is the original code. It works okay-ish on one size phone (I used the emulator's Nexus s here), but good luck getting it to work across phone sizes (try it on a Nexus 5x for example), or being able to control scrolling, or resizing elements. I couldn't get the scrolling to be reliably 1 page wide on all phones, and strangely the magnification of the transformer wasn't centered and also varied between phones.
I had a simple idea that a 'ViewPager' would fit the full width of the diplay, and it would just somehow 'know' to center the current item, etc.
I am embarrassed to say I lost 3 days to this mess. To find what was causing the issues for me, I had to completely comment out all the code and uncomment it, bit by bit, to explain to myself what is going on.
In the other branch, "BetterCoverflow", is the simplified code. Slightly smaller in size, platform-independent, and with fewer code smells.
Note that there's not much difference code-wide between these examples. The biggest change came in my shifting point of view.
The viewPager is not expected to go the full width of the display and yet manage only a subset. It's not expected to manage centering etc. That's the job of the enclosing View.
Many of the smells have been removed (addOnGlobalLayoutListener? Trying to 'kick' the ViewPager as the transformer wasn't, somehow, being used when first laid out...
though I'm sure not all...no tests? I mean really...).
As with most libraries we automatically reach for, Android-CoverFlow wasn't actually needed in the end, just a better understanding of how ViewPagers work.
I hope this article helps some other poor, lost soul struggling with a ViewPager-based coverflow implementation.
Don't be an Idiot (like me...)
I don't feel particularly intelligent this evening.
For the last several work days, I've been fighting a bug. A pernicious bug based around security...which I couldn't find.
I'd go forwards over my code, and then backwards. Up and down. I'd check web headers, recompile and run all my tests, etc... No joy, still couldn't see the bug.
What was it?
Picture that you've got a class like this:
Later on, in another object I instantiated one of these bad boys:
What would you expect to see in the console? Obviously:
What I'd meant is for the output to be:
Can you see where I went wrong?
The getName method should have been "protected"", at least, or maybe even "public", with a friendly and handy @Override annotation to indicate that we're overriding a method in the ancestor.
I said to myself, "Self," I says, "you done stepped on your crank."
This is an obvious bit of code, right?
But what if you had this:
Now, the output is:
How can you tell if that's any different to
The fun thing is that there was a third party library handling encryption (that has caused us issues before), as well as a web API that is (by design) rather uncommunicative.
All it would say in this instance is "Bad signature." Exactly accurate, but not terribly helpful.
All the web request headers, as well as the other 6 arguments as well as those of the other 8 involved web calls were all correct.
The biggest cause (of my stupidity, I'll admit) is that everywhere else in the code, I did this:
You'll notice that I refer back to a common definition of what getRealName is supposed to do. There were three different encryption methods (each of which overrode getRealName()), and lots of other variables involved...but this was the cause.
Instead of using the common name manufacturing functions, I'd reimplemented them marking them private, for some reason. Private methods aren't, of course, overridden. Why would they be?
To make matters worse, I'd done it in a place where I was almost guaranteed to not find the issue.
Damn, another few grey hairs.
Don't be an Idiot (like me)...invent your own way. This one's mine.
I gave a talk to our department at work a week or two ago, and thought that I'd make the talk available here.
The talk is purposefully abstract and simplistic, as I feel that the Circus architecture is simple and applicable to other platforms as well as Android. I simply didn't want people to get too hung up on the platform-flavour for the talk.
If you have any questions, comments or suggestions, feel free to contact me at email@example.com.
So, I had a problem.
Like every mobile project before, this project was suffering.
The views (UIViewControllers on iOS or Activities on Android) lifecycles were wreaking havoc with the logic. The views were like waves....they'd arrive causing my logic to start, and they'd go tearing down the logic sandcastles as they left.
The code invariably had train wrecks scattered throughout: object.attribute.field.method(). On Android it would all crash horribly in a nullPointerException fire. On iOS, it would just quietly disappear like a mafia hit.
The code was so tightly integrated, you needed testing magic, mocking doubles, stubs, libraries, Robolectric or such to try to test the code. These often came with limitations that were worse than the untested code.
So, we're back at manual testing bug whack-a-mole: knock one bug down, and another one rises. Knock the second bug down, and the first one returns. Squish both, and a yet-undiscovered bug pops up.
The UI state for each view was this flexible thing...when a button was clicked, the textfield was enabled. Except when the 'Bad Password' screen was showing, because then the cursor would show through. So we had to special-case that. Of course, when the requirement came down to show a 'forgotten password' dialog, that had to be catered for as well, and...
I had to be missing something fundamental here.
At a previous company, I was floundering towards a solution...
I had a class called a 'Brain'. This Brain was a wrapper for the logic states, while the views were the OS-specific visible part. The brain could be in a different state, and the corresponding views would be shown.
Oh, this raised issues to be sure. Both iOS and Android want their framework to be the center of your application. After all, it makes an iOS app near-impossible to translate to Android, and vice-versa, unless you're using a multi-platform framework (Zen, PhoneGap, etc). I had these states, but the interface was more than 3 methods (it had 5 per state) that people coming to learn it thought it was crazy.
Hrm...time for more research, I guess...
I ran across this article(http://hannesdorfmann.com/android/model-view-intent) by Hannes Dorfmann. Brilliant article, and it felt like I was on to something. It's for Android, obviously, but I'm sure the concepts will translate with a bit of work.
Then I downloaded the code...and felt trapped back in the same box. Not being completely up-to-speed on RXJava, it felt like there were things just out the corner of my eye that I didn't understand or couldn't even completely see. I didn't see how it would handle the lifecycle issues I'd been facing.
So, then, I thought: why not rewrite it using the most boring, stupid java possible?
That's when I arrived at 'Circus'. (Think Piccadilly, not Big-Top)
Before I tell you what Circus is, let me ask you a question: What /is/ your app?
Is it the views? Is it the database? Is it the network?
I'd say it's the *logic* of your app that makes it special. How you choose to do whatever it is your app does.
What are the features I'm looking to implement?
1) Eliminate lifecycle gyrations from my logic.
2) Isolate the storage, network, and all other library calls I'm not writing.
3) Provide an easy event-recording mechanism.
4) Ensure that all of my UI, my logic, and my plugins can be written using TDD /without/ having to use crazy testing frameworks.
5) On Android, ensure that my tests run in the JVM, so they're crazy fast, enabling real TDD again.
6) Split the UI, Logic, and Plugins into pieces so that multiple people can work on the same codebase in a *clean* way.
Sound too good to be true?
Okay, so lots of lines. How's this really a solution for anything?
Well, let's simplify the views...the views only ever do two things: they send an event to the back end, and they render new states. They do *not* alter global state. They could be killed, and reconstituted, and still be just as good as before, as they do not maintain their own state.
Okay, let's also simplify the back end things. In fact, let's break the back-end things into component parts. There's a 'network' plugin that provides the 20 or so network calls we'll make to the backend. There's a 'database' call that handles all storage of temporary state. And so on. Any code that we aren't writing gets wrapped in a plugin. All of it.
What's left? Our logic, of course. This is the thing that makes each view of our app a view of OUR app.
How do these all talk to each other? In true Uncle-Bob fashion, though Interfaces (or your language-equivalent). This means that there's one (and only one) way from a view to send an event to our logic. There's only one way for our logic to send a new state to the front end for rendering. There's only one way for our logic to kick off a network call, or for that network call to return its result.
This means that testing is a doddle, as we can simply write any old object that implements this interface, and all of a sudden, it's that kind of object. We don't need Mockito, Robolectric or any other framework to test...it's all just Our Code!
Our tests can accept events, and send new states to the UI through the UI Interface. Our tests can send events to the Back classes and see what states pop out. The plugins can be tested using the Plugin Interface.
And threading? The UI reports events through a single call. It receives new states through a single call. When UI events are reported, they're simply put onto a background thread, and when a new state is sent to the views to be rendered, it's shifted to the foreground thread. That means that our code *never* needs to bother with foreground/background, etc. Our logic can simply block and wait, as we're by definition on a new background thread for each event, and always on the foreground thread for all UI changes.
Will this be a solution for all ills? I don't know.
In my next few articles, I'm going to demonstrate these principles, and how I might implement all this and more. What I'm aiming to deliver is a simple framework that we can use to get our jobs done in a way that allows for full (and easy) TDD in a mobile context.
My needs are a fairly simple Android application, so I'll be writing it in Java. The concepts would hold on iOS as well with slight variations.
In fact, if I manage the abstractions cleanly enough, the logic won't care a bean what the UI is doing, nor will the UI care what the backend is...
So, enough for today. Read my next article for first implementations...