API Design and Naming Guidelines
From Xojo Documentation
This a living document that will be updated periodically when necessary.
Contents
Introduction
The purpose of this document is to provide guidelines for creating APIs and naming them such that new APIs are clear, easy to understand and most importantly, consistent with the rest of the Xojo framework.
The goal of consistency is to make it possible for a user to guess the API of a class with which they have never worked rather than have to learn each one every time. This also makes understanding existing code faster as well. However, consistency should not reduce readability.
Consistency
Before creating a new API, review the existing APIs to see if there’s one that is similar to what you need for the new functionality.
Case
Class names are upper camel case.
Example: TextField
Class members are upper camel case.
Example: TextField.SelectedText
Parameters are camel case.
Example: ListBox.AddRow("First Name")
Namespaces are upper camel case, when used.
Example: Xojo.Core
General Naming
Names should provide the most intuitive identification not necessarily the most accurate. They should choose clarity over brevity. The name should make it immediately obvious what the item is for even if that name is not entirely accurate. For example, previously the name of the event that is called when a button is clicked was called Action rather than Clicked or Pressed because it can be triggered without clicking/pressing the button. That makes Action more accurate but it's also not at all intuitive. In API 2.0, Action has been renamed Pressed which, while less accurate, is far more intuitive. It's obvious what the event is for and the exceptions (such as the fact that a button can be triggered without pressing it - via accessibility features for example) are easy enough to understand. Another example is the Volume class in the context of FolderItems. Most people have to learn what this means. In the API 2.0 we use Drive instead since most volumes are Hard Drives, Flash Drives or Solid State Drives (SSD). Volume is more accurate but not as intuitive since it also has a meaning relating to sound volume.
Names should not use abbreviations or truncations unless they are so common that they are spoken that way. For example, Info is acceptable. Sel (for Selection) is not. Abbreviated Min and Max are not acceptable as part of compound framework terms. This does not apply to standard math functions, such as for Min
and Max
, which will will not change.
Class names should be a noun. For the case when there are two classes, one that can be modified and one that cannot, use the Editable prefix for the one that can be modified and no prefix for the one that cannot be modified. For example, Image
and EditableImage
.
Method names should be in the form of verb noun. For example AddInterval
.
Functions (methods that return a value) should be named or prefixed with what they return. DesktopFolder
is acceptable. GetDesktopFolder
is not.
Event names should be past tense if the event itself has already taken place when the user’s code is called. Example: TextField.TextChanged
is acceptable because the TextField’s value has already been changed.
Event names should be in the form noun verb. For example SelectionChanged
.
Most controls should have a Pressed event. Controls whose primary form of user interaction involves the user clicking or tapping the control should have a Pressed event that is passed a parameter when necessary to indicate what was pressed.
Properties (and Constants) in most cases, should be nouns and named after what they contain.
Enumeration names should always be plural. Name of members of type enumeration should always be singular. For example, the Timer class has RunModes
as an enumeration and RunMode
as a member of type RunModes
.
Omit needless words. For example, System.GetNetworkInterface
should just be NetworkInterface
(and also because functions should be named what they return). TextField has both TextChanged
and SelectionChanged
.
Group associated members. When possible, if members are associated with each other, use a common prefix so they are listed together alphabetically. For example, DragEntered
and DragExited
. However, do this only when other API guidelines can also be satisfied.
When a class member is specific to a particular platform, it should be prefixed with the name of the platform. The following are the designated prefixes: Mac, Windows, Linux, iOS, Android, Web and Mobile.
Boolean Properties
These prefixes are used with UI-related Boolean properties for situations when it is unclear how the UI may be changed.
Name | Description | Example |
---|---|---|
Allow | Makes a change to the behavior or interface possible but not necessarily immediately. | AllowFocusRing
|
Has | Makes an immediate visual change to an element of the user interface of the object. | HasCloseButton
|
Is | Optionally used for read-only Boolean properties (and functions) when needed for clarity. | IsRectangle
|
If Allow or Has results in awkward names, use something that is better.
Otherwise Boolean properties do not have a prefix and are named after what they indicate. For example, Enabled
or DarkModeIsSupported
.
Lists
List items are accessed via a value from the list or a 0-based index. When an index is used, the preposition “At” is appended which differentiates it from a method that looks up by value. Classes that provide access to a list should implement the following standard set of members and make use of the appropriate nouns to identify what is being changed. For example, AddRow would be used for controls that deal with rows, AddPanel for controls dealing with tabs or panels, etc.
Name | Description | Example |
---|---|---|
AddRow | Adds a single item to the end of the list. | AddRow("Hello")
|
AddRowAt | Adds a single item to the list at the specified index. | AddRowAt(5, "Hello")
|
AddAllRows | Adds all items passed to the end of the list. | AddAll(myList)
|
FirstRowIndex | The index of the first row. | Var first As Integer = Me.FirstRowIndex
|
RowCount | Returns the number of items in the list. | Var count = Me.RowCount
|
RowValue | When list items are objects, this method accesses the object specified by the name that is passed. | RowValue("SaveButton").Visible = False
|
RowValueAt | Accesses the value specified by the index(s) passed. Use "Value" for simple types (numbers, String, etc.). Omit "Value" for objects. So just use RowAt if this returned an object. | Var value As Integer = Me.RowValueAt(5)
|
RowTag | When list items are objects that have tag properties, this method accesses the tag for the item specified by the value passed. | value = RowTag("Hello")
|
RowTagAt | Accesses the tag for the item specified by the index(s) passed. | RowTagAt(5) = "Hello"
|
RemoveRowValue | Removes the first item specified by the value passed. Call repeatedly to remove any additional rows with the same value. | RemoveRowValue("Hello")
|
RemoveRowAt | Removes the item specified by the index passed. | RemoveRowValueAt(5)
|
RemoveAllRows | Removes all items from the list. | RemoveAllRows
|
LastRowIndex | The index of the last row. | Var lastRow As Integer = Me.LastRowIndex
|
LastAddedRowIndex | The index of the last row added by AddRow, AddRowAt or AddAllRows. If no items have been added, an OutOfBoundsException is raised. | Var last As Integer = Me.LastAddedRowIndex
|
For example the Toolbar class contains a list of buttons so it should implement the above listed methods, but use "Button" in place of "Row". The Dictionary class is considered a list, just an unordered one, so it should also implement the relevant methods above, without a qualifying noun. List classes should also always implement the Iterable and Iterator interfaces to allow looping with For Each...Next statements.
For two dimensional lists (probably only for multi-column listboxes), the *ValueAt
method should take row and column indexes as parameters and could return the entire row (as an array of the appropriate type) if no column index is passed.
For list-type containers that have multiple things that are counts (row count, column count, for example) then be specific for all the types of count: RowCount, ColumnCount.
For user interface controls that are list-oriented (ListBox, PopupMenu, TabPanel, Toolbar, SegmentedControl, etc.) where the user can select an item from the list, the following members should be implemented (again, substituting an appropriate noun in place of "Row" if necessary):
Name | Description | Example |
---|---|---|
SelectedRowValue | Returns the value of the item currently selected. If no row is selected, an OutOfBoundsException is raised. | Var value As String = Me.SelectedRowValue |
SelectedRowIndex | Allows you to get or set the selected row. If you call this as a function and no row is selected, an OutOfBoundsException is raised. | Var row As Integer = Me.SelectedRowIndex
|
SelectedRowCount | Returns the number of rows selected. | If Me.SelectedRowCount > 0 Then
|
Avoid hidden functionality. Avoid negative indexes for special behaviors. Instead add methods that provide the functionality. For example, Listbox.CellAt(-1, -1)
is bad as it should really raise an OutOfBoundsException. Instead, a Listbox.Contents
method that returns a two-dimensional array would be better. With no parameters this is the entire ListBox, with one parameter this is the specific row.
Errors
Errors should raise exceptions rather than return error codes. However, the exceptions raised can have members that provide error codes. Methods should not return booleans to indicate that they succeeded or not as that boolean value is really a simple error code. Instead, the function should throw an exception if it fails.
Enumeration Usage
Use enumerations in place of integer constants when the value is unimportant and they are not used as part of a calculation. For example, bit flags would need to be constants because they are used to calculate actual Integer values.