Movatterモバイル変換


[0]ホーム

URL:


Jane Street Style Guide

Generalities

This guide documents what you could call Jane Street’shouse style.It’s not an absolute guide to how code is written everywhere here;different groups make different decisions in some cases. We’ll documentsome of the variations here, while noting which one of those we think ofas the house style.

Even though the house style isn’t universally followed, it’s a gooddefault, and it’s the style we use in our most foundational libraries,includingBase,Core,Async andIncremental. Indeed, thoselibraries are generally good places to look at for examples of goodpractice.

Formatting

  • Indentation should follow the rules ofocp-indent. This isenforced by default by Jenga, and is included in Jane Streetdefault vim and emacs configs, but you can also achieve it byusingocp-indent with theJaneStreet ruleset.

  • Formatting should follow the rules ofocamlformat with theJaneStreet profile.

  • 90 characters is the maximum line length.

Naming

  • Identifiers (constructors, variables, structures, type names, …)are written using underscores to separate words, not inCamelCase.So writenum_apples notnumApples andFoo_bar, notFooBar.

  • Identifiers that exist for short scopes should be short, e.g.,

    List.iter~f:(funx->x+1)numbers

    whereas identifiers that live for large scopes should be larger andmore descriptive.

  • Boolean-returning functions should have predicates for names.For instance,is_valid is better thancheck_validity.

  • Useunsafe to indicate memory-unsafety only. e.g., thefollowing, fromBase.Array, is an appropriate use of unsafe.

    (** Unsafe version of [set].  Can cause arbitrary behavior when     used for an out-of-bounds array access. *)externalunsafe_set:'at->int->'a->unit="%array_unsafe_set"

    This function, however, fromBase.Int_intf, usesuncheckedinstead of unsafe to indicate a function that may have badbehavior in certain circumstances, but is not memory unsafe.

    (** [of_float_unchecked] truncates the given floating point     number to an integer, rounding towards zero. The result     is unspecified if the argument is nan or falls outside     the range of representable integers. *)valof_float_unchecked:float->t

Comments

  • Use OCamldoc style comments – starting with(** – in mli’s. Makesure to use square-brackets to enclose small OCaml values, and {[]} to enclose larger blocks.

  • Avoid comments that add no useful information to the type andfunction name.i.e., avoid this:

    (** Compares two strings *)valcompare:string->string->int

    Whereas this would be better.

    (** [compare x y] Compares two strings lexicographically *)valcompare:string->string->int

    If you really can’t find anything useful to add with a comment, it’sacceptable to have a comment that is redundant with the type andname, particularly in broadly-aimed libraries like Base and Core.This reflects the fact that it’s considered gauche in some circlesto have functions with no documentation whatsoever. As an example,in the Bytes module, we might have this

    (** [length t] returns the number of bytes in [t]. *)vallength:t->int

    even though the content is largely duplicative.

Let-syntax

When programming with monadic and applicatives, the use oflet%bind isgenerally preferred in cases where an explicit variable is bound. For example,prefer this:

let%bindx=fooyinlet%bindz=barxinreturnx+z

to

fooy>>=funx->barx>>=funz->returnx+z

That said, even when usinglet%bind orlet%map, infix operators arestill useful when operating in a point-free style, i.e., when notbinding variables. So, we might write.

let%bindx=m>>|Model.xinfoox

rather than

let%bindx=(let%mapm=minModel.xm)infoox

Opening Modules

Top-Level

Only open modules with a clear and standard interface, and open all suchmodules before defining anything else, e.g.,

openCoreopenAsyncletsnoo()=...

not:

letnow()=...(* shadowed below by opening Time *)openCoreopenTime

Local “open”

Even using a local “open” will pollute the namespace of an expressionand risk silently shadowing a variable of the same type. When you do usea local-open, you should aim to keep the scope small. This notation:

letfg=Time.(now()<lockout_time)

is generally preferable to this one:

letfg=letopenTimeinnow()<lockout_time

because the scope is more clearly delimited.

That said, when the interface being opened has a standard and widelyunderstood API, then thelet open syntax is preferred.

letopenOption.Monad_infixinload_config()>>=create

Some modules provide anInfix module or anO module which isspecifically designed for local opens.

Signatures

  • Most modules should contain a single type namedt. For instance,theString modules definesString.t. When you’re reading theString interface and you seet, think ‘string’ in your head.

  • Prefer functions that return explicit options (or errors) overthrowing exceptions. If your function routinely raises anexception, put_exn in the name. For example:

    valcreate:string->toptionvalcreate_exn:string->t
  • Functions in a moduleM should typically takeM.t as their firstargument.

    An exception to this is that optional arguments should be placedbefore theM.t argument if that is the sole positional argument,to allow the optional arguments to be erased.

  • Prefer standard signature includes to hand-written interfaces.E.g., prefer this:

    includeComparable.Swithtypet:=t

    over this:

    valt_of_sexp:Sexp.t->tvalsexp_of_t:t->Sexp.t
  • Most comments should be for users of a module, not implementers,which means that most comments should be in themli. We placecomments above the function signature, module, or record fielddescribed. Small comments can also go to the right of a line ofcode.

Defensive Programming

  • Always annotate the type of ignored values. That way the compilerwill complain if the type changes. For example, imagine what happensto

    ignore(Unix.wait`Any);

    when

    valwait:[`Any|`PidofPid.t]->Pid.t

    changes to return the exit code instead of raising on non-zero:

    valwait:[`Any|`PidofPid.t]->Pid.t*Exit.t

    It’s better to write:

    ignore(Unix.wait`Any:Pid.t);

    The same logic applies to underscores, except where the type ismore-or-less pinned down anyway.

  • If a function takes two arguments of the same type and the argumentsare used differently, they should usually be labeled to avoidaccidental permutation. Notable exceptions includeList.append and(-) where the order is sufficiently clear. For example:

    valsend:source:User.t->dest:User.t->unit
  • Avoid catch-all cases in pattern matches. For example, prefer this:

    letposition_change=function|Executione->Dir.sign(Execution.dire)*Execution.sizee|Ack_|Out_|Reject_->0

    to this.

    letposition_change=function|Executione->Dir.sign(Execution.dire)*Execution.sizee|_->0

    Both are correct, but the former will produce an error ifCorrection is added to the type being matched on, and the latterwon’t.

  • Optional arguments should typically only be used for functions thatare called in many different places. That’s because optionalarguments make your code less explicit, which makes the call sitesharder to understand, and it makes it easy to forget to specify theargument in a case where it’s required.

    A good rule of thumb is to avoid optional arguments for functionsthat are not exposed in your module interface.

Directory Names

Use dashes (“-”) in for multi-word directory names, instead ofunderscores (“_”). Thus:

~/local/trunk/trader-tools/

instead of:

~/local/trunk/broken_directory_name/

Exceptions

It is OK for a function to raise an exception in an exceptionalcircumstance. We often, but not always, suffix such functions with_exn. Although raising is fine, one should not write code that dependson which exception is raised. That is, one should never declareexceptions or match on them. Doing so is problematic in a large codebase because the type system doesn’t track which exception is raised. Ifcallers of a function need to discriminate among error cases, then thefunction should return a variant distinguishing the cases.

Instead of declaring new exceptions, one should use a function thatimplicitly constructs an exception, e.g.:

raise_s[%message"something bad happened"(t:t)]

Tests

Test your code. Some codebases do better with having unit tests forevery function, some are better with end-to-end tests, but generallyspeaking all significant changes should have tests demonstrating theireffect.

Tests can sometimes go in the same file, but typically should go in aseparate test library. This is preferable for a number of reasons: Forone thing, it encourages you to write tests against the exposed API ofyour code, which is usually the right approach. It also allows you todraw on more dependencies in your test code than you might want to linkin to your production code.

Expect tests are the preferred way of writing tests. This doesn’t meanthat you shouldn’t use Quickcheck or other forms of property tests; it’sjust thatlet%expect_test is a single umbrella under which you canwrite all of these kinds of tests conveniently.

Private submodules

A number of modules expose aPrivate submodule. User code shouldnot use functions in aPrivate submodule.Private submodulescontain functionality that is internal to the implementation, intendedfor use in closely-related code like expects tests and benchmarks ofthe module itself. Such code is often in another library and needsaccess to private internals of the main module.


[8]ページ先頭

©2009-2025 Movatter.jp