Omnis Studio has a very long history - longer than most other database software. Omnis Studio is up to version 3.1, but version 1 was crafted out of a product called merely Omnis, at version 7. Omnis version 1 was a product for the Apple II, which then moved to the Macintosh, the PC and then of course various Unices.

Despite this tremendous legacy, Omnis has maintained almost 100% backwards compatibility. The biggest leap in my experience has been from "Omnis Classic" (i.e. version 7) to Studio.

Now, anyone with experience in the computer industry will know, "backwards compatibility" means "doing things in a certain way because that way made sense ten years ago", and the same is true for Omnis. I have been trained as a computer scientist, and the following things bug me about Omnis Studio code:

Omnis code is not like any programming language you have ever used - to edit a record, for example, you say Prepare for edit, then any number of calculations to fill in the fields (of the form Calculate {fieldname} as {expression}), then Update files.

Global variables. Omnis is full of them. Although you can make your own scoped variables, all the instructions work and rely on global variables. For example, both Prepare for edit and Update files set "the flag" on an error. Exceptions? We don't need no steenking exceptions!

Non-existent object library. As far as system provided objects go, you get an object to work with files (that represents both an open file, and static methods for files), an object to deal with fonts, and a timer object. That's it. Everything else is bare primitives. Even networking support is based around commands like TCP Listen.

There is a thing called "notation", that lets you say things like $clib.$windows.W_AddressBook.$open() instead of Open window W_Addressbook, but its integration is poor - hover your mouse over a variable, and see the contents. Hover your mouse over notation, and see "Notation".

The way to declare a method or variable as public is to put a dollar sign in front of the name.

There is no way to bail out of the $construct method if you're passed stupid initial values.

Omnis has a horrible habit of ignoring errors entirely. From referring to variables that don't exist to database updates that fail, Omnis will quite gleefully continue whatever it's doing.

Abysmal object model. Apart from tables (called "files"), Omnis has "lists", which are sort of a table in memory, rather than a LISP or Python-style list. To refer to a the value in the column named foo in the current line of the list, we say list.foo. However, if the columns are not named, we can say list.C1, list.C2, and so forth. If we want to talk about foo in line 17 rather than the current line, we say list.17.foo. If we want to talk about the column foo (type, size, etc), we say list.$cols.foo.

Abysmal method syntax. To search for a row in the list where the value in column foo is 5, we say Do list.$search(list.foo=5,kTrue,kFalse,kFalse,kFalse). The expression is evaluated for each row in the list, while the other parameters are evaluated at call time. Needless to say, there's no way to make your own methods with this kind of syntax.