There And Back Again on Code Comments

It’s interesting how learning is sometimes like travel. At the end of the journey, you end up back where you started, only with a broadened perspective.

Trivial example: code comments. As a new programmer, I commented my code extensively. This was probably to compensate for my difficulties in reading code. It also allowed me to be lazy in the code I wrote. There isn’t as much of a need for clear and readable code when there are always plain English comments to fall back on.

Then I joined a team that was militantly against comments. Every comment was an admission of a failure to write readable code. I took this on-board and disciplined myself not to rely on comments. This forced me to spend more time on refactoring my code to make it as readable and “self-documenting” as possible. I still wouldn’t say I’m there yet, but I’m confident that my code is much more readable now than it was then.

However, in the last year or so, I’ve started adding comments again. Not too many, but here and there to add more context. It felt like blasphemy at first. Some of my comments are even somewhat redundant, but I’ve since learned that encoding the same information in redundant ways aids comprehension. For example, traffic lights use both colour and position to encode the same information.

Superficially it seems as though I’ve returned to where I started: I’m commenting code again. However, if I hadn’t disciplined myself to do without comments for a while, I wouldn’t have been forced to learn how to write more readable code. Abstaining from code comments is a forcing function for more readable code. At some point, you can re-integrate commenting as a useful tool rather than a crutch.

Interestingly, I’m confident that if I could travel back in time and tell past-me or any of my old teammates that, actually, comments can be a valuable tool to aid comprehension, I’m positive I would encounter strong resistance. This is just as it should be; I don’t think it can be any different. When you’re learning a new skill, it’s necessary to become somewhat closed off and follow a direction single-mindedly for a while. It’s part of the learning process. If you were too suggestible and deviated too easily, you would go around in small circles and never get anywhere.

You see the same pattern across the development community when a new technology or framework is introduced (I won’t name names. I’m sure you can think of some examples). In the early days, there is hype and zealotry. Then over time as more developers adopt the tech into production and real issues with it emerge, there is disillusionment and abandonment. Finally a more realistic, calibrated picture of the trade-offs of the technology emerge.


Hype Cycle graph by Jeremykemp at English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=10547051, Middle Earth photo by Henry Xu on Unsplash

Forcing Functions in Software Development

Here’s an unavoidable fact: the software project you’re working on has some flaws that no one knows about. Not you, your users, nor anyone in your team. These could be anything from faulty assumptions in the UI to leaky abstractions in the architecture or an error-prone release process.

Given enough time, these flaws will be discovered. But time is money. The sooner you discover them, the cheaper they are to fix. So how do you find out about them sooner?

The good news is that there are some things you can do to force issues up to the surface. You might already be doing some of them.

Here are some examples:

  • Dig out an old or cheap phone and try to run your app on it. Any major performance bottlenecks will suddenly become obvious
  • Pretend you’re a new developer in the team1. Delete the project from your development machine, clone the source code and set it up from scratch. Gaps in the Readme file and outdated setup scripts will soon become obvious
  • Try to add support for a completely different database. Details of your current database that have leaked into your data layer abstractions will soon become obvious
  • Port a few screens from your front-end app to a different platform. For example, write a command-line interface that reuses the business and data layers untouched. “Platform-agnostic” parts of the architecture might soon be shown up as anything-but
  • Start releasing beta versions of your mobile app every week. The painful parts of your monthly release process will start to become less painful
  • Put your software into the hands of a real user without telling them how to use it. Then carefully watch how they actually use it

To borrow a term from interaction design, these are all examples of Forcing Functions. They raise hidden problems up to consciousness in such a way that they are difficult to ignore and therefore likely to be fixed.

Of course, the same is true of having an issue show up in production or during a live demo. The difference is that Forcing Functions are applied voluntarily. It’s less stressful, not to mention cheaper, to find out about problems on your own terms.

If your Android app runs smoothly on this, it’ll run smoothly on anything.

If you imagine your software as something evolving over time, strategically applying forcing functions is a way of accelerating this evolutionary process.

Are there any risks in doing this? A forcing function is like an intensive training environment. And while training is important, it’s not quite the real world (“The Map Is Not the Territory“). Forcing functions typically take one criteria for success and intensify it in order to force an adaptation. Since they focus on one criteria and ignore everything else, there’s a risk of investing too much on optimizing for that one thing at the expense of the bigger picture.

In other words, you don’t want to spend months getting your mobile game to run buttery-smooth on a 7 year old phone only to find out that no one finds the game fun and you’ve run out of money.

Forcing functions are a tool; knowing which of them to apply in your team and how often to apply them is a topic for another time.

However, to give a partial answer: I have a feeling that regular in-person tests with potential customers might be the ultimate forcing function. Why? Not only do they unearth a wealth of unexpected issues like nothing else, they also give you an idea of which other forcing functions you might want to apply. They’re like a “forcing function for forcing functions”.

Or to quote Paul Graham:

The only way to make something customers want is to get a prototype in front of them and refine it based on their reactions.

Paul Graham – How to Start a Startup

If you found this article useful, please drop me a comment or consider sharing it with your friends and colleagues using one of the buttons below – Matt (@kiwiandroiddev)


1 Thanks to Alix for this example. New starters have a way of unearthing problems not only in project setup, but in the architecture, product design and onboarding process at your company, to give a few examples.

Cover Photo by Victor Freitas on Unsplash

Android Device Nudge Detection Helper Class

I recently added a feature to StarCraft 2 Build Player to start playing build orders when the users’ phone is nudged. The idea is so you don’t have to waste precious seconds looking down at your phone to tap the “Play” button, instead you can just mindlessly bump your phone on your desk and you’re off.

Anyway, it turned out to be pretty easy to factor this into a reusable class so here it is:

[sourcecode language=”java”]
package com.kiwiandroiddev.sc2buildassistant;

import java.util.ArrayList;

import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;

/**
* Class for reporting when the device’s acceleration (excluding gravity) exceeds
* a certain value. Compatible with all Android versions as it uses Sensor.TYPE_ACCELEROMETER
* rather than Sensor.TYPE_LINEAR_ACCELERATION.
*
* NudgeDetector objects are initially disabled. To use, implement
* the NudgeDetectorEventListener interface in your class, then register it
* to a new NudgeDetector object with registerListener(). Finally, call
* setEnabled(true) to start detecting device movement. You should add a call
* to stopDetection() in your Activity’s onPause() method to conserve battery
* life.
*
* @author kiwiandroiddev
*
*/
public class NudgeDetector implements SensorEventListener {

private ArrayList<NudgeDetectorEventListener> mListeners;
private Context mContext;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private boolean mEnabled = false;
private boolean mCurrentlyDetecting = false;
private boolean mCurrentlyChecking = false;
private int mGraceTime = 1000; // milliseconds
private int mSampleRate = SensorManager.SENSOR_DELAY_GAME;
private double mDetectionThreshold = 0.5f; // ms^-2
private float[] mGravity = new float[] { 0.0f, 0.0f, 0.0f };
private float[] mLinearAcceleration = new float[] { 0.0f, 0.0f, 0.0f };

/**
* Client activities should implement this interface and register themselves using
* registerListener() to be alerted when a nudge has been detected
*/
public interface NudgeDetectorEventListener {
public void onNudgeDetected();
}

public NudgeDetector(Context context) {
mContext = context;
mListeners = new ArrayList<NudgeDetectorEventListener>();
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}

// Accessors follow

public void registerListener(NudgeDetectorEventListener newListener) {
mListeners.add(newListener);
}

public void removeListeners() {
mListeners.clear();
}

public void setEnabled(boolean enabled) {
if (!mEnabled && enabled) {
startDetection();
} else if (mEnabled && !enabled) {
stopDetection();
}
mEnabled = enabled;
}

public boolean isEnabled() {
return mEnabled;
}

/**
* Returns whether this detector is currently registered with the sensor manager
* and is receiving accelerometer readings from the device.
*/
public boolean isCurrentlyDetecting() {
return mCurrentlyDetecting;
}

/**
* Sets the the amount of acceleration needed to trigger a "nudge".
* Units are metres per second per second (ms^-2)
*/
public void setDetectionThreshold(double threshold) {
mDetectionThreshold = threshold;
}

public double getDetectionThreshold() {
return mDetectionThreshold;
}

/**
* Sets the minimum amount of time between when startDetection() is called
* and nudges are actually detected. This should be non-zero to avoid
* false positives straight after enabling detection (e.g. at least 500ms)
*
* @param milliseconds_delay
*/
public void setGraceTime(int milliseconds_delay) {
mGraceTime = milliseconds_delay;
}

public int getGraceTime() {
return mGraceTime;
}

/**
* Sets how often accelerometer readings are received. Affects the accuracy of
* nudge detection. A new sample rate won’t take effect until stopDetection()
* then startDetection() is called.
*
* @param rate must be one of SensorManager.SENSOR_DELAY_UI,
* SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_GAME,
* SensorManager.SENSOR_DELAY_FASTEST
*/
public void setSampleRate(int rate) {
mSampleRate = rate;
}

public int getSampleRate() {
return mSampleRate;
}

/**
* Starts listening for device movement
* after an initial delay specified by grace time attribute –
* change this using setGraceTime().
* Client Activities might want to call this in their onResume() method.
*
* The actual sensor code uses a moving average to remove the
* gravity component from acceleration. This is why readings
* are collected and not checked during the grace time
*/
public void startDetection() {
if (mEnabled && !mCurrentlyDetecting) {
mCurrentlyDetecting = true;
mSensorManager.registerListener(this, mAccelerometer, mSampleRate);

Handler myHandler = new Handler();
myHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mEnabled && mCurrentlyDetecting) {
mCurrentlyChecking = true;
}
}
}, mGraceTime);
}
}

/**
* Deregisters accelerometer sensor from the sensor manager.
* Does nothing if nudge detector is currently disabled.
* Client Activities should call this in their onPause() method.
*/
public void stopDetection() {
if (mEnabled && mCurrentlyDetecting) {
mSensorManager.unregisterListener(this);
mCurrentlyDetecting = false;
mCurrentlyChecking = false;
}
}

// SensorEventListener callbacks follow

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

@Override
public void onSensorChanged(SensorEvent event) {
// alpha is calculated as t / (t + dT)
// with t, the low-pass filter’s time-constant
// and dT, the event delivery rate

final float alpha = 0.8f;

mGravity[0] = alpha * mGravity[0] + (1 – alpha) * event.values[0];
mGravity[1] = alpha * mGravity[1] + (1 – alpha) * event.values[1];
mGravity[2] = alpha * mGravity[2] + (1 – alpha) * event.values[2];

mLinearAcceleration[0] = event.values[0] – mGravity[0];
mLinearAcceleration[1] = event.values[1] – mGravity[1];
mLinearAcceleration[2] = event.values[2] – mGravity[2];

// find length of linear acceleration vector
double scalarAcceleration = mLinearAcceleration[0] * mLinearAcceleration[0]
+ mLinearAcceleration[1] * mLinearAcceleration[1]
+ mLinearAcceleration[2] * mLinearAcceleration[2];
scalarAcceleration = Math.sqrt(scalarAcceleration);

if (mCurrentlyChecking && scalarAcceleration >= mDetectionThreshold) {
for (NudgeDetectorEventListener listener : mListeners)
listener.onNudgeDetected();
}
}
}

[/sourcecode]

The reason I stuck to using Sensor.TYPE_ACCELEROMETER was because I want to support Froyo with my app. If you’re only targeting 2.3 (API level 9) and higher, you could use Sensor.TYPE_LINEAR_ACCELERATION, and simplify this code a fair bit by stripping out the gravity calculation in onSensorChanged(), etc.

Feel free to use this in your projects. Drop me a comment if you spot bugs or have any suggestions.

Data on Android device supported features

I’ve recently been experimenting with OpenGL ES 2.0 on Android for a graphical app (some excellent guides can be found at http://www.learnopengles.com/). So far so good. It turns out that gone are the days of countless fixed function calls like glBegin() glVertex3f() glColor4f() for sending vertex data, nowadays you use shaders for everything and send your vertex data to OpenGL in large chunks.  Supposedly this makes the graphics driver software a lot simpler to write and leads to better performance overall. Keeping track of all of those calls and their corresponding closing calls could end up a bit of a headache so it seems like it provides some benefit to application developers too.

Before diving in and using ES 2.0 exclusively (well, at first anyway – code for ES 1.x support can always be added later) I wanted to get an idea of how widely ES 2.0 is supported across Android devices because it could have a big effect on the market size for my app.

After filtering through some anecdotal evidence on Stackoverflow, not surprisingly the best place to find this data was straight from the horse’s mouth at the Android Dashboards page.

According to the data, ES 2.0 support is over 90% and it seems reasonable to assume it’s only going to increase in time. So that settles it – OpenGL ES 2.0 it is.

The Dashboards page also has data on the installation base for each Android version which may also be very useful to you during the research phase of developing your app.