|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe reason for this article to appear is the constant praise of Symbian OS by its developers and constant complaints of developers, who try to use this platform. It is said that a wise person admits his mistakes. But to admit the mistake, one should first understand it. The problem of Symbian OS is exceptionally bad design, in spite of 2 years of development spent only for creating the architecture. I'll try to explain the general and specific design faults, focusing mostly on C++ development. An experience of Symbian OS programming is required to understand this article. The average knowledge of standard C++ and STL is required to understand the code examples. Also, it is required to understand, that not only mobile devices, but the developers themselves have very limited memory and processing power! General faults (sorted from the worst to the subtle)ComplexityAccording to documentation, Symbian OS is "a large OS, containing hundreds of classes and thousands of member functions". It seems to be one of the most complicated operation systems ever built. Basic Symbian OS class hierarchy contains 1201 classes. Series 60 SDK adds 293 classes and UIQ SDK adds another 705 classes! There are many classes with hundreds of methods in their interface, and less then 10 methods per class is very uncommon. No human being can fully comprehend this kind of a system. Inability to detect things, common to different devicesSymbian OS has a common core, but the user interface part of the system interface classes are completely different. But in most places they should be identical! Let's compare the dialogs on Series 60 and on UIQ. Both have title bar with some text. Both have "control elements", listed from top to bottom. Both have associated commands (in "options menu" on Series 60 and "buttons row" on UIQ). Control elements are presented to the user to view and modify text, numbers, dates, times, etc.. They can look different and be operated by the stylus on the first platform and by soft-keys on the second one, but from the point of view of the application they behave the same. It does not matter how the command was presented and selected by the user, if it properly triggers appropriate methods. This generic approach to dialogs is best seen in J2ME. It has nothing to do with the Java language. This is just an example of good architecture against a bad one. It is funny, that third party developers had created the set of headers, which allows the Series 60 program to compile and run on UIQ! It is very strange, that OS creators could not understand the urge of common UI structure. It could be extended (adding new control elements, commands, etc.), but it should not have to be rewritten. The problem is not only in code reusability (complete reusability is possible for most dialogs, for example), but in "developer reusability" also. It is difficult for humans to learn and understand new concepts, remember hundreds of class names and thousands of method signatures. Making the key decision based on false facts and statementsThe best example of this is exception handling in Symbian OS. The concise C++ exception mechanism was dropped and ugly "cleanup-stack" was picked, based on the claim, that "C++ exception makes the compiled code to grow 40% in size". This comparison is made in this way: the program is compiled with or without exception support compiler option and then the size is compared. But the program, which uses exceptions, is designed to use exception as a safety mechanism. When exception handling is switched off, the program is smaller, but it is not safe. Many researches have found, that after another safety mechanism is added (checking for error codes, for example), not only the compiled code returns to its original size, but also the source code grows considerably. After the C++ exceptions mechanism was dropped, C++ was faltered, and the other ugly things followed - two-stage construction (see Stroustrup book [^] Appendix E for explanation, why two-stage construction is bad thing), T-, R-, and C- classes with the usage restrictions, which the compiler cannot detect, prohibition of specific cases of multiple inheritance, etc. until the language stops to be C++. Then the "experts" on the forum said: "when you start to program Symbian, you should forget everything and start from the scratch". What this really means is "we’ve made horrible mistakes, and you cannot write code in a clear and convenient way". If the cleanup stack is implemented in OS as the exception cleanup mechanism, then inserting Let’s see the code, generated “under hood” by the standard C++ compiler, assuming that the cleanup mechanism is implemented via cleanup stack. The classic approach to ensure proper cleanup is done in the following way: every object instance should be a member, reside in stack or be owned by exactly one class Example { Resource one; Resource two; int simple_value; std::auto_ptr<Aggregate> aggregate; public: explicit Example(int data): one("One"), two("Two"), simple_value( 5 ), aggregate( new Aggregate(data) ) { // CODE1 - can throw an exception } void operation() { aggregate->operation(); } }; Under the hood, the following code would be generated by the compiler (I use C language as a portable assembler for explanation): struct Example { Resource one; Resource two; auto_ptr__example aggregate; }; void example__destructor(Example * this) { aggregate__destructor( &aggregate ); resource__destructor( &two ); resource__destructor( &one ); } void example__constructor(Example * this, int data) { resource__constructor( &this->one, "One" ); CleanupStack::Push( &this->one, resource__destructor ); resource__constructor( &this->two, "Two" ); CleanupStack::Push( &this->two, resource__destructor ); // use the knowledge that int has no destructor // and its contructor cannot throw an exception this->simple_value = 5; Aggregate * ag = malloc_or_throw_bad_alloc( sizeof(Aggregate) ); // protect against exception in aggregate__constructor CleanupStack::Push( ag, free ); aggregate__constructor( ag, data ); CleanupStack::PopMemory( ag ); // Use the knowledge, that inline auto_ptr constructor // cannot throw an exception auto_ptr__example__construct( &this->aggregate, ag ); CleanupStack::Push( &this->aggregate, aggregate__destructor ); // CODE1 - can throw an exception CleanupStack::Pop( 3 ); } void example__operation() { aggregate__operation( aggregate.ptr ); } Later, if the void test() { Example ex(1); // CODE2 - can throw an exception if( condition ) return; // CODE3 - can throw an exception } And the compiler generates: void test() { Example ex; example__constructor( &ex, 1 ); CleanupStack::Push( &ex, example__destructor ); // CODE2 - can throw an exception if( condition ) { CleanupStack::Pop( 1 ); example__destructor( &ex, 1 ); return; } // CODE3 - can throw an exception CleanupStack::Pop( 1 ); example__destructor( &ex, 1 ); } If we compare compiler-generated code to the Symbian manual-written code, which does the same thing, we can see that the generated code looks very similar to the code, which every Symbian developer writes everyday, but it is sometimes faster (!), as part of the objects can be effectively stored in stack or made class members, instead of allocating them in heap. Source code for standard C++ is short and simple, and it is guaranteed to cleanup everything! Note the comments in the generated code to see how the compiler intelligently uses its knowledge of destructor and constructor code (if they are Note, that the correct cleanup does not depend on how the exceptions themselves are implemented. If arbitrary classes cannot be thrown due to some limitations (this requires a form of dynamic type identification), then Lack of documentation and clear examplesThis is just the consequence of the complexity. But things could be better, if the examples were the source code of real programs. The program, which demonstrates hundred types of lists, is not a programming example. It looks pretty well for the non-technical person (probably a manager in Symbian), who can run and play it. But the developer, who looks for information in source code, finds nothing helpful. Static dataThe documentation explains lack of static data in this way: "Implementation is difficult, as DLL could reside in ROM, and so we do not have the place to store static data. Besides, the global variables are bad for OOP". While the global variables are considered a bad thing in OOP, the class static variables (and module static variables) are not. In fact, class static variables are very common thing in OOP to store the information common for class instances. The implementation impossibility is also a false claim, but to understand it, let’s consider the following example, written in standard C++. class Example { static int state; }; And then (when the state is required). int s = Example::state;
Under the hood, the compiler adds static data to data segment. It looks, like this: struct DataSegment { ... int example_state; }; And later, to access it, the compiler generates: int s = get_data_segment()->example_state; where class MyDocument : public CAknDocument { ... int example_state; }; And then (when the state is required) int s = static_cast<MyDocument *>( CEikonEnv::Static()->EikAppUi()->Document() )->example_state; It is surprising, but from a low-level point of view, there is very little difference in these two approaches! As traditional OSs keep track of data segment, Symbian OS application manager keeps track of document. Placing all the static data in the Document manually, the Symbian developer is again making the job of the compiler tougher! But this time the developer is in a worse situation. Consider the following code: template<class T> class RequiresStatic { typedef T state_type; ... static state_type state; }; In standard C++ for every used instantiation of struct DataSegment { ... RequiredStatic<int> But the Symbian developer is unable to do the same thing and add these variables to Strange (not technical) design goalsSymbian developers were creating an "Object oriented" system, rather than a useful one. "Object oriented" is not a synonym for "Good". OOP is just a method, which the developer is required to master to build a system which is easier to devise, code, document and (later) understand. Symbian OS complexity is apparently a good indication of wrong application of object oriented approach. C++ Standard Library and STLSTL is basically the library of generic containers and algorithms with iterators gluing them together. Symbian developers decided to implement their own library of containers (they completely ignored algorithms and most iterators, though). The documentation informs that the reason for non standard implementation is efficiency. Let's consider the Symbian implementation of strings, arrays and lists. Strings in Symbian are called "descriptors". They are templates and each descriptor can hold a sequence of 8 or 16 bit characters. There are basically two groups of descriptors - "modifiable" and "non-modifiable", the former is inherited from the latter. Strange enough, non-modifiable descriptor allows complete replacement of content (similar to pointer semantic of In each group, different flavors of descriptors exist. The base descriptor for modifiable descriptors is The type There are a multitude of array and buffer classes in Symbian. Some arrays use single flat memory chunk and others use several segmented chunks. They differ in effectiveness of different operations the same way as If Document-View architectureSymbian OS boasts of Document-View architecture with ResourcesUsing resources is in general a dangerous thing, because it separates the code into several files and several programming languages. The system should provide the way to bind the C++ code and resource definitions and this is not an easy thing! Resources are useful, when you can change them without changing the executable. But how can you really accomplish this? If you add the button to the resource, how do you add behavior? It is impossible. Then editing resources become very limiting - you can add static labels and you can define constants (like translating text to the other language or modifying the slider maximum value). But as you cannot define the parts of a resource, you should copy all its structure and using this feature for translation becomes costly. Later, when the structure changes (splitting dialog in two tabs, for example), you should carefully repeat this on every translation. The inherent problem of resource is that you should accurately match the code with the resource. Look, the J2ME program (which creates all controls in the code) is several times shorter, than the Symbian C++ program plus its resources. And believe me, defining unique constants for control identifiers is just another job of the compiler, not of the developer. By the way, resource definition language is another language, which we should learn (and it is far from perfect).
| ||||||||||||||||||||||||||||