summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBert Belder <bertbelder@gmail.com>2019-01-15 20:30:43 +0100
committerBert Belder <bertbelder@gmail.com>2019-01-28 17:43:26 +0100
commit21687325f0ed0928bc398c6381d30f0b1b775ce6 (patch)
tree12510c88ba60df0251b8423168699853b0bb6e6f
parentd2a336ee02649184c0e6c8e81bebc8e5fec1a4a0 (diff)
Add API design guidelines (denoland/deno_std#119)
Original: https://github.com/denoland/deno_std/commit/bef7ba14303997a05cdd1ea0d28ffefeee75c993
-rw-r--r--README.md104
1 files changed, 104 insertions, 0 deletions
diff --git a/README.md b/README.md
index 03bcae0fa..aab0f4522 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,110 @@ Example: Instead of `file-server.ts` use `file_server.ts`.
More specifically, code should be wrapped at 80 columns and use 2-space
indentation and use camel-case. Use `//format.ts` to invoke prettier.
+### Exported functions: max 2 args, put the rest into an options object.
+
+When designing function interfaces, stick to the following rules.
+
+1. A function that is part of the public API takes 0-2 required arguments,
+ plus (if necessary) an options object (so max 3 total).
+
+2. Optional parameters should generally go into the options object.
+
+ An optional parameter that's not in an options object might be acceptable
+ if there is only one, and it seems inconceivable that we would add more
+ optional parameters in the future.
+
+3. The 'options' argument is the only argument that is a regular 'Object'.
+
+ Other arguments can be objects, but they must be distinguishable from a
+ 'plain' Object runtime, by having either:
+
+ - a distinguishing prototype (e.g. `Array`, `Map`, `Date`, `class MyThing`)
+ - a well-known symbol property (e.g. an iterable with `Symbol.iterator`).
+
+ This allows the API to evolve in a backwards compatible way, even when the
+ position of the options object changes.
+
+```ts
+// BAD: optional parameters not part of options object. (#2)
+export function resolve(
+ hostname: string,
+ family?: "ipv4" | "ipv6",
+ timeout?: number
+): IPAddress[] {}
+
+// GOOD.
+export interface ResolveOptions {
+ family?: "ipv4" | "ipv6";
+ timeout?: number;
+}
+export function resolve(
+ hostname: string,
+ options: ResolveOptions = {}
+): IPAddress[] {}
+```
+
+```ts
+export interface Environment {
+ [key: string]: string;
+}
+
+// BAD: `env` could be a regular Object and is therefore indistinguishable
+// from an options object. (#3)
+export function runShellWithEnv(cmdline: string, env: Environment): string {}
+
+// GOOD.
+export interface RunShellOptions {
+ env: Environment;
+}
+export function runShellWithEnv(
+ cmdline: string,
+ options: RunShellOptions
+): string {}
+```
+
+```ts
+// BAD: more than 3 arguments (#1), multiple optional parameters (#2).
+export function renameSync(
+ oldname: string,
+ newname: string,
+ replaceExisting?: boolean,
+ followLinks?: boolean
+) {}
+
+// GOOD.
+interface RenameOptions {
+ replaceExisting?: boolean;
+ followLinks?: boolean;
+}
+export function renameSync(
+ oldname: string,
+ newname: string,
+ options: RenameOptions = {}
+) {}
+```
+
+```ts
+// BAD: too many arguments. (#1)
+export function pwrite(
+ fd: number,
+ buffer: TypedArray,
+ offset: number,
+ length: number,
+ position: number
+) {}
+
+// BETTER.
+export interface PWrite {
+ fd: number;
+ buffer: TypedArray;
+ offset: number;
+ length: number;
+ position: number;
+}
+export function pwrite(options: PWrite) {}
+```
+
### Use JS Doc to document exported machinery
We strive for complete documentation. Every exported symbol ideally should have