Sunday, June 20, 2010

Nokia, the next Geoworks?

Summary: Nokia gave a profit warning, because their high-end phones do not sell well. This post compares the problems of Symbian, Nokia's high-end phone OS, to Steve Yegge's analysis of why Geoworks went bankrupt.



What Geoworks?

Geoworks was a software company which wrote a windowing system and applications in assembler. In the end, it went bankrupt in the end of nineties. When Steve Yegge wrote about the benefits of high-level languages, he used Geoworks as an example how using low-level languages takes a toll on business.

His argument is that low-level languages make optimization impossible and implementing features slow. After the system reaches a critical point in complexity, usability starts to suffer. User see sluggishness, bugs and lack of features.


...But it's because we wrote fifteen million lines of 8086 assembly language. We had really good tools, world class tools: trust me, you need 'em. But at some point, man...

The problem is, picture an ant walking across your garage floor, trying to make a straight line of it. It ain't gonna make a straight line. And you know this because you have perspective. You can see the ant walking around, going hee hee hee, look at him locally optimize for that rock, and now he's going off this way, right?

This is what we were, when we were writing this giant assembly-language system. Because what happened was, Microsoft eventually released a platform for mobile devices that was much faster than ours. OK? And I started going in with my debugger, going, what? What is up with this? This rendering is just really slow, it's like sluggish, you know. And I went in and found out that some title bar was getting rendered 140 times every time you refreshed the screen. It wasn't just the title bar. Everything was getting called multiple times.

Because we couldn't see how the system worked anymore!


Which is higher-level language, C or C++?

One benchmark of language level is how many lines of code are needed to implement a feature. In high-level languages, the compiler does more work. The programmer has to write less code. This means that implementation is faster. There are also less bugs, since the lines of code which were not needed don't contain bugs, and because debugging is easier in small haystack.

C language is infamous for being low-level. Therefore it's paradoxical that Symbian Open C is a advertised as a productivity tool. But sadly it really is a productivity tool compared to Symbian C++.

The examples below demonstrate why. The snippets below read a configuration variable from a file. The scenario is that we want to run automatic system tests on a communication protocol and to automate the selection of an access point. It is stored in format "accesspoint=Winsock". The important thing here is the length of the listing, not the exact content.



// Read a configuation variable with 35 lines of code.
_LIT8(KAccessPointId, "accesspoint=");
TBool ReadAccessPointNameL(const TDesC& aFileName, TDes& aResult)
{
RFs fs; // File session
RFile file; // File handle
TBool apNameFound = EFalse;

// Connect to file server
User::LeaveIfError(fs.Connect());
CleanupClosePushL(fs);

// Open file for reading
if (file.Open(fs, aFileName, EFileWrite) == KErrNone)
{
CleanupClosePushL(file);
// Read the file to memory (we can't use line-by-line
// reading with TTextFile, since it can't handle 8-bit text)
TInt size = 0;
User::LeaveIfError(file.Size(size);
HBufC8* content = HBufC8::NewLC(size);
User::LeaveIfError(file.Read(content->Des());

// Find the start and end of the access point name.
TInt start = content.Find(KAccessPointId());
if (start > KErrNotFound)
{
start = start + KAccessPointId.Length();
TInt end = start;

// Find the next newline.
do {
end++
} while(end < content.Length() &&
(*content)[end] != '\r' &&
(*content)[end] != '\n');
}

// Save the result.
aResult.Copy(content.Mid(start, end - start));
apNameFound = ETrue;
}

CleanupStack::PopAndDestroy(content);
CleanupStack::PopAndDestroy(&file);
}

CleanupStack::PopAndDestroy(&fs);
return apNameFound;
}



The same in C:



// Read a configuation variable with 20 lines of code.
const char* access_point_id = "accesspoint=";
char* read_access_point_name(const char* file_name)
{
char* result = NULL;
// Open file for reading.
FILE* file = fopen(file_name, "r");
if (file) {
char line[200];

// Read line by line and search for access point variable.
while(!result && fgets(file, line, 200)) {
if (strstr(line, access_point_id) == line) {

// Remove newline.
int len = strlen(line);
while (line[len - 1] == '\r' || line[len - 1] == '\n') {
line[len--] = 0;
}

// Get the value of the variable.
reuslt = strdup(line + strlen(access_point_id);
}
}
fclose(file);
}
return result;
}



Symbian C++ was written before people really knew how to do object-oriented programing. They completely botched all the APIs. The horrible descriptors were designed to counter memory overflows, which C functions don't check. Nowadays they just clutter the code. Symbian takes pride in being a microkernel OS, so they require the programmer to connect to servers to start sessions. This adds further lines. The exception handling with cleanup stacks vomits more useless lines. And if you think this is ugly, you haven't seen anything, like the use of active objects in the socket interface.

One C++ selling point is the syntax for classes. Well, Symbian has a 68-page coding convetions which gives very explicit rules how to name classes and which functions they should at least have. This nitpicking makes classes heavy structures, and decimates any advantage from syntactic sugar. Virtual functions are the only part of C++ which wasn't assaulted. Even templates were banned as too error-prone.

So Posix C really is a higher-level language. Just for comparison, here is the same in Ruby.



# Read a configuration variable in 9 lines of code.
def readAccessPointName(fileName)
file = File.open(fileName, "r")
file.each_line do |line|
if (line =~ /accesspoint=(.*)/)
# The (.*) in regular expression caught
# the access point name to $1.
return $1.chomp
end
end
raise 'No access point name in file ' + fileName
end



So it is unsurprising that App store contains 225000 appplicaitons while Ovi store contains just thousands.


GeoWorks attempted to get third party developers but was unable to get much support due to expense of the developer kit — which ran $1,000 just for the manuals — and the difficult programming environment, which required a second PC networked via serial port in order to run the debugger. (source)


But it's the user experience that counts...

Symbian phones are famous for having equal features but lower usability than iPhone. To demonstrate how difficult programming is visible in usability, I'll tell you about usging the file browser to read log files. The plain text viewer has several defects. If you open a large log, it announces out of memory error and shuts down. It underlines randomly some content which it thinks might be a link. It can't choose a small font to show lots of content, so you only see a few lines at a time. Luckily, there has been some progress in plain text viewer. Earlier, it used to crash with medium-sized files. Now it either shows it or announces error.

The way I see it, these defects reflect the difficulty of the programming platform. Usually programmers have some professional pride, which makes them fix errors and usability defects with time. What could be stopping it? We can only speculate.
  • Customizing the UI component which shows text would require too much work, since platform doesn't support dynamic loading and presentation.
  • Low level language necessitates big project sizes. This dilutes responsibility so that no one is responsible for the plain text viewer in the "buck stops here" sense.
  • There is a culture of fixing only showstopper bugs and leaving others there, since there isn't time to fix all bugs, as fixing a single bug is slow.

This way, we get "multimedia computers" which can't display plain text.

It doesn't have to be this way

Nokia does fine in low-end phones, which use the closed Series40 OS. Also the Maemo/Meego platform is promising, however the phone in N900 is still fresh software, creating issues in sound quality and usability. They haven't had time to finalize the phone. In compatibility with major desktop operating systems, Maemo's Linux kernel runs circles around Symbian. This will show up in usability sooner or later. You can always strip down the user interface to produce a simpler phone which is easier to use, but you can't put the solid Linux infrastructure to a Symbian phone.

In the long run, I'm optimistic about Nokia's future. Once they finalize the phone on Maemo and scrap the Symbian platform, they'll be fine. If you want to capitalize on this, the right time to buy Nokia shares is just before they start selling their next Meego phone. However, make sure that the press agrees that Meego has good phone, battery life and usability - if they botch them on Meego, they won't recover. However, I'm not putting my money where my mouth is, because I have enough economic Nokia risk in my life already.

2 comments:

J Kujala said...

Good post. I liked the examples. I think reading one configuration variable should take one line of code, like python-like
pickle.load(open("file","r"))["access_point"]

Simo said...

Thanks. Naturally it is possible to write a function read_variable(file, variable_name) in any language, but sooner or later you have to face fundamental APIs and language syntax.

Pickle is almost there: on the one hand, it is part of core Python APIs and does allow reading a variable in one line. On the other hand, the files are not robustly human editable with a text editor:

(dp0
Vaccess_point
p1
VWinsock
p2
s.