diff --git a/collects/scribblings/style/acknowledgment.scrbl b/collects/scribblings/style/acknowledgment.scrbl new file mode 100644 index 0000000000..ea748b20b3 --- /dev/null +++ b/collects/scribblings/style/acknowledgment.scrbl @@ -0,0 +1,9 @@ +#lang scribble/base + +@title{Acknowledgment} + +The rules borrow from many sources. While many helped the authors survive +their own coding experience, the first author also conducted focus sessions +with the members of PLT and these sessions produced many insights about +coding style. Jacob Matthews took the time to write up his thoughts on +testing, and they are much appreciated. diff --git a/collects/scribblings/style/constructs.scrbl b/collects/scribblings/style/constructs.scrbl index fe25b97c62..032837d34f 100644 --- a/collects/scribblings/style/constructs.scrbl +++ b/collects/scribblings/style/constructs.scrbl @@ -39,6 +39,32 @@ racket ] ] +@compare[ +@racketmod[#:file +@tt{good} +racket + +(define-syntax (increment! stx) + (syntax-case stx () + [(_ s sn fn i) + (with-syntax ([w (r #'s)]) + (define g (ff #'sn #'w)) + ...)])) +] +@; ----------------------------------------------------------------------------- +@racketmod[#:file +@tt{bad} +racket + +(define-syntax (increment! stx) + (syntax-case stx () + [(_ s sn fn i) + (with-syntax ([w (r #'s)]) + (let ([g (ff #'sn #'w)]) + ...))])) +] +] + @; ----------------------------------------------------------------------------- @section{Conditionals} @@ -81,7 +107,6 @@ Of course you should also favor @scheme[cond] (and its relatives) over Keep expressions small. Name intermediate results. - @compare[ @racketmod[#:file @tt{good} @@ -101,3 +126,152 @@ racket (sqr (posn-y p))))) ] ] + +@; ----------------------------------------------------------------------------- +@section{Structs vs Lists} + +Use @racket[struct]s when you represent a combination of a fixed number of +values. Don't use lists. + +@; ----------------------------------------------------------------------------- +@section{Lambda vs Define} + +While nobody denies that @racket[lambda] is cute, @racket[define]d +functions have names that tell you what they compute and that helps +accelerate reading. + +@compare[ +@racketmod[#:file +@tt{good} +racket + +(define (process f) + (define (complex-step x) + ... 10 lines ...) + (map complext-step + (to-list f))) +] +@; ----------------------------------------------------------------------------- +@racketmod[#:file +@tt{bad} +racket + +(define (process f) + (map (lambda (x) + ... 10 lines ...) + (to-list f))) +] +] + + +@; ----------------------------------------------------------------------------- +@section{List Traversals} + +With the availability of @racket[for/fold], @racket[for/list], + @racket[for/vector], and friends, programming with for @racket[for] loops + has become just as functional as programming with @racket[map] and + @racket[foldr]. With @racket[for*] loops, filter, and termination clauses + in the iteration specification, these loops are also far more concise than + explicit traversal combinators. And with @racket[for] loops, you can + decouple the traversal from lists. + +@compare[ +@;% +@(begin +#reader scribble/comment-reader +[racketmod #:file +@tt{good} +racket + +;; [Sequence X] -> Number +(define (sum-up s) + (for/fold ((sum 0)) ((x s)) + (+ sum x))) + +;; examples: +(sum-up '(1 2 3)) +(sum-up #(1 2 3)) +(sum-up (open-input-string "1 2 3")) +]) + +@; ----------------------------------------------------------------------------- +@;% +@(begin +#reader scribble/comment-reader +[racketmod #:file +@tt{bad} +racket + +;; [Listof X] -> Number +(define (sum-up s) + (for/fold ((sum 0)) ((x s)) + (+ sum x))) + +;; example: +(sum-up '(1 2 3)) +]) +] + + Note: @racket[for] traversals of user-defined sequences tend to be + slow. If performance matters in these cases, you may wish to fall back on + your own traversal functions. + +@; ----------------------------------------------------------------------------- +@section{Functions vs Macros} + +Use functions when possible; do not introduce macros. + +@compare[ +@racketmod[#:file +@tt{good} +racket +... +;; Message -> String +(define (message-name msg) + (first (second msg))) +] +@; ----------------------------------------------------------------------------- +@racketmod[#:file +@tt{bad} +racket +... +;; Message -> String +(define-syntax-rule + (message-name msg) + ;; ===> + (first (second msg))) +] +] + +@; ----------------------------------------------------------------------------- +@section{Parameters} + +If you need to set a parameter, use @racket[parameterize]: + +@compare[ +@racketmod[#:file +@tt{good} +racket +... +;; String OutputPort -> Void +(define (send-to msg op) + (parameterize + ((current-output-port op)) + (format-and-display msg)) + (record-message-in-log msg)) +] +@; ----------------------------------------------------------------------------- +@racketmod[#:file +@tt{bad} +racket +... +;; String OutputPort -> Void +(define (send-to msg op) + (define cp + (current-output-port)) + (current-output-port op) + (format-and-display msg) + (current-output-port cp) + (record-message-in-log msg)) +] +] diff --git a/collects/scribblings/style/style.scrbl b/collects/scribblings/style/style.scrbl index 1f4a43d468..a11293a229 100644 --- a/collects/scribblings/style/style.scrbl +++ b/collects/scribblings/style/style.scrbl @@ -59,3 +59,4 @@ Also, we encourage everyone to look over the commit messages. If you see @include-section["constructs.scrbl"] @include-section["textual.scrbl"] @include-section["branch-and-commit.scrbl"] +@include-section{acknowledgment.scrbl} diff --git a/collects/scribblings/style/textual.scrbl b/collects/scribblings/style/textual.scrbl index 19553af1a2..81039813cd 100644 --- a/collects/scribblings/style/textual.scrbl +++ b/collects/scribblings/style/textual.scrbl @@ -220,8 +220,46 @@ These lines help both writers and readers to orient themselves in a file. Use meaningful names. The Lisp convention is to use full English words separated by dashes. Racket code benefits from the same convention. +@compare[ +@;% +@(begin +#reader scribble/comment-reader +[racketmod #:file +@tt{good} +racket + +render-game-state + +send-message-to-client + +traverse-forest +]) + +@; ----------------------------------------------------------------------------- +@;% +@(begin +#reader scribble/comment-reader +[racketmod #:file +@tt{bad} +racket + +rndr-st + +sendMessageToClient + +trvrs-frst +]) +] +@; +If you cannot give a unit a good name, consider the possibility that it +isn't a proper unit of code. + +You may use dots between parts of names, e.g., @racket[p.x] to suggest that +field @racket[x] is selected from struct @racket[p]. + In addition to regular alphanumeric characters, Racketeers use a few -special characters. +special characters by convention, and these characters indicate something +about the name: @;column-table[ @col[? ! "@" ^ %] @col[1 2 3 4 5] @col[1 2 3 4 5] ] @@ -233,3 +271,7 @@ special characters. @row["@" units a@] @row[^ signatures a^] ] + +Some Racketeers use the suffix of the name to suggest type(-like) +information, e.g., @racket[body-xexpr] or @racket[body-xml]. For such uses, +a colon is commonly found, e.g., @racket[p:posn] or @racket[p:pair-of-numbers]. diff --git a/collects/scribblings/style/unit.scrbl b/collects/scribblings/style/unit.scrbl index ce89cf75f6..86c88cf98d 100644 --- a/collects/scribblings/style/unit.scrbl +++ b/collects/scribblings/style/unit.scrbl @@ -27,14 +27,55 @@ If a unit of code looks incomprehensible, it is probably too large. Break division; consider alternatives. @; ----------------------------------------------------------------------------- -@section{Module Interfaces} - -The purpose of a module is to provide some services. +@section{Modules and their Interfaces} +The purpose of a module is to provide some services: +@; @centerline{Equip a module with a short purpose statement.} @; Often ``short'' means one line; occasionally you may need several lines. +In order to understand a module's services, organize the module in three +sections below the purpose statement: its imports, its exports, and its +implementation: +@;% +@(begin +#reader scribble/comment-reader + (racketmod #:file + @tt{good} + racket/base + +;; the module implements a tv server + +(require 2htdp/universe htdp/image) + +(provide + tv-launch + tv-client) + +(define (tv-launch) + (universe ...)) + +(define (tv-client) + (big-bang ...)) +)) +@;% + If you choose to use @racket[provide/contract], define auxiliary concepts + related to the contracts between the @racket[require] and the + @racket[provide] sections. A test suite section---if located within the + module---should come at the every end, including its specific + dependencies, i.e., @racket[require] specifications. + +@; ----------------------------------------------------------------------------- +@subsection{Require} + +With @racket[require] specifications at the top of the module, you let + every reader know what is needed to understand the module. The + @racket[require] specification nails down the external dependencies. + +@; ----------------------------------------------------------------------------- +@subsection{Provide} + A module's interface describes the services it provides; its body implements these services. Others have to read the interface if the external documentation doesn't suffice: @@ -150,12 +191,29 @@ Consider using @scheme[provide/contract] for module interfaces. contracts (constructor predicates that check only a tag); they tend to cost relatively little. +@subsection{Uniformity of Interface} + +Pick a consistency rule for the names of your functions, classes, and +methods. Stick to it. For example, you may wish to prefix all exported +names with the same word, say @racket[syntax-local]. + +Pick a consistency rule the parameters of your functions and methods. Stick +to it. For example, if your module implements an abstract data type (ADT), +all functions on the ADT should consume the ADT-argument first or last. + +@subsection{Sections} + Finally, a module consists of sections. It is good practice to separate the sections with comment lines. You may want to write down purpose statements for sections so that readers can easily understand which part of a module implements which service. Alternatively, consider using the large letter chapter headings in DrRacket to label the sections of a module. +With @racketmodname[rackunit], test suites can be defined within the + module using @racket[define/provide-test-suite]. If you do so, locate the + test section at the end of the module and @racket[require] the necessary + pieces for testing specifically for the test suites. + @; ----------------------------------------------------------------------------- @section{Classes & Units} @@ -163,3 +221,5 @@ Finally, a module consists of sections. It is good practice to separate the @section{Functions & Methods} +If your function or method consumers more than two parameters, consider +keyword arguments so that call sites can easily be understood.