You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

169 lines
5.2 KiB

=====================
API design guidelines
=====================
Introduction
============
A consistent API design makes it easier to write applications: it should be
easy to remember API call naming, argument order, and other conventions, to
make it as easy as possible to write error-free code.
API design decisions involve several important concerns:
* Consistency: easy to remember API calls and arguments.
* Versioning: easy to extend the API, avoid dead ends in design.
* Footprint: minimize code footprint of Duktape internals and application
call site.
* Performance: some API designs allow better performance than others.
This document provides some notes on common API design issues and trade-offs.
The document is not intended to be complete, but just cover practical issues
as they come up.
Naming
======
API calls
---------
All API calls are lowercase, underscore separated, and begin with the prefix
``duk_``. Duktape internals, not part of the public API, also have calls
matching this convention. The public API calls are documented in the public
API header (actually a segment of ``duktape.h`` in the distributable) and the
API documentation.
Existing convention for operating on value stack items:
* ``duk_get_XXX()`` gets a value without modifying the value on the stack.
If the value doesn't match expected type, some default value (like NULL
or zero) is returned.
* ``duk_require_XXX()`` gets a value without modifying the value on the stack.
If the value doesn't match expected type, an error is thrown.
* ``duk_to_XXX()`` coerces a value in-place into desired type.
Varargs API calls:
* Named with a trailing ``_va()``, for example ``duk_error_va()``.
- Existing exception: ``duk_push_sprintf()``.
Existing convention for safe/protected calls:
* All calls are unsafe by default, i.e. may throw errors.
* ``duk_pXXX()`` calls are protected, i.e. they catch errors. API call
return value indicates success or error, and error is typically passed
via the value stack.
* ``duk_safe_XXX()`` calls are safe, i.e. don't throw, but they don't
provide a success/error indication.
- Existing exception: ``duk_safe_call()`` doesn't currently match this
convention: it's a protected call (catches an error and returns a
success/failure indicator).
Arguments
---------
Lowercase, underscore separated. No other strict guidelines, some current
names:
* ``ctx``
* ``idx``, ``obj_index``, ``from_index``, ``to_index``, ``index1``, ``index2``
* ``arr_index``
* ``flags``, ``enum_flags``
* ``len``, ``ptr``, ``key``
Avoid the argument names:
* ``index``: conflicts with a function from strings.h.
Typing
======
Return values
-------------
* ``duk_bool_t`` for true/false return values.
* ``duk_idx_t`` for value stack indices.
* ``duk_int_t`` for general return values which need to express both signed
and unsigned values.
* ``duk_ret_t`` for Duktape/C function return values.
Arguments
---------
* ``duk_idx_t`` for value stack indices.
* ``duk_uarridx_t`` for Ecmascript array indices.
* ``duk_size_t`` for byte lengths (strings, buffers, etc).
* ``void *`` for generic void pointers.
* ``const char *`` for string data pointers.
* ``void *`` for buffer data pointers.
Varargs API calls
-----------------
Vararg API call variants are provided when it's awkward for an application to
use non-vararg API call variants. For example, it'd be awkward to construct a
formatted string first and then push it using ``duk_push_string()``, which is
why ``duk_push_sprintf()`` is provided.
Flags vs. variants
==================
A common issue is whether there should be an API call which provides a wide
variety of behavior controlled via flags, or a corresponding set of invididual
API calls.
Individual API calls are generally favored in the API:
* They make call sites easier in applications as there's no need to remember
and combine flag defines.
* Individual API calls can be easily mapped to either individual internal
implementations, or to internal helpers which take flags, whichever is most
appropriate for the internal implementation. In contrast convering an API
call taking flags into individual internal calls is difficult: while it's
possible to check the flags in a macro call site, the macro will quite easily
end up evaluating flags multiple times which violates API macro guarantees.
* They enable best performance because no flags field is built or decoded.
* Even when improved calls are added to the API, individual calls are easy to
map to new providers, so versioning is easy even though some API clutter is
then easily left behind.
There are situations where fewer calls with behavioral flags are preferable:
* If the API call provides a lot of functionality, so that the set of
individual call variants would be very large, flags may be more appropriate.
* If the API call behavior is likely to evolve new control flags over time,
flags may be more appropriate.
For example, ``duk_def_prop()`` fits both of these criteria. The downside of
using flags is that:
* It's not easy to decode the flags in an API macro and call invididual
internal implementations if that would otherwise be convenient.
* Passing in flags has a small performance cost, and decoding the flags in
the call target has a relatively larger performance cost.