File: CodingGuidelines

package info (click to toggle)
wings3d 2.4.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 14,404 kB
  • sloc: erlang: 132,169; ansic: 3,779; lisp: 1,434; sh: 805; makefile: 794; cpp: 244; xml: 50
file content (339 lines) | stat: -rw-r--r-- 11,370 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
The most important coding rule is to imitate the existing code
(that is always a good idea in any project you are contributing
to). If you add or modify code in existing modules, try to match
the style of the surrounding code in the module.

First a few general rules that perhaps go without saying:

- Make sure that your code compiles without warnings.

- Make sure that there are no calls to undefined functions.
  In the Wings shell, use wx(). (Use wh() to show a list of
  other useful utility commands.)

- This is not really a coding guideline, but don't forget
  Bjorn's Law: "If it isn't tested, it doesn't work." This law
  has proved itself to be correct many times in the fifteen years
  or so since I formulated it. There is no change to the code
  (or Makefile) that is so trivial that it can't possibly be wrong
  and does not need testing.

- Don't sacrifice clarity for efficiency, unless it is truly
  needed. Make sure that you measure (use the ?TC() macro) to
  find the real bottleneck before you start optimizing. Even good
  programmers are usually wrong when they try to guess where the
  where the bottlenecks are located.

  There some hints about efficiency in the guidelines below. For
  more about efficiency, see 

      http://www.erlang.org/doc/efficiency_guide/part_frame.html

And now over to more concrete rules:

- Indent Erlang code using a mixture of tabs and spaces,
  and interpret tabs as up to 8 spaces (this is the default
  settings for the Erlang mode in Emacs).

  It is also OK to indent using spaces only, if you use an
  editor where it is difficult to mix tabs and spaces.

  What is not OK is to change the definitions of tabs in your
  editor, because that will make indentation look different
  for everybody else.

- Each new level should be indented 4 more positions than
  the previous one (default for the Erlang mode in Emacs).

- One percent character ("%") is used for comments *only* following
  a line of code. Never place single percent characters at the
  beginning of a line. Example:

     -record(some_record,
             {bar,                         %List of bar objects
	      foo			   %Keep track of the foos
              .
	      .
	      .
 	      }).

- Use two percent characters ("%%") for comment blocks before
  a function or inside a function. Thus the percent characters are
  either at the beginning of a line or preceded by white-space.
  See src/wings_va.erl for the recommend way to comment exported
  API functions.

- Use three percent characters ("%%%") for comments that apply
  to more than one function. For instance like this:

     %%%
     %%% Local functions.
     %%%

  to indicate that the rest of the file contain local functions.

- Try to use at most 80 characters per line.

- Never comment out deleted code. Delete it from the file. We
  use Git to keep track of the history of the file. Explain in
  the check-in comment why you deleted the code. (The only
  acceptable exception is *frequently* used io:format/2 calls used
  for debugging.)

- Place the exported functions (and no local function) at the
  beginning of the module. Local functions should follow all exported
  functions. (This rule apply to new modules. Do not change all modules
  that break that rule at once. We will do it step-by-step when doing
  other changes to avoid huge diffs in the Git repository.)

- Place one blank line between each function definition, except possibly
  for related one-line functions.

- Do not use -import. Use fully qualified calls -
  Module:Function(Arguments...) - instead.

- But do import functions (such as map/2, reverse/1, and foldl/3) from
  the 'lists' module. Importing those functions (that can be thought
  of being part of the language) improves readability so much that
  it justifies breaking the general rule. Further justification:
  Robert Virding does it this way. (Robert Virding is one of the
  original inventors of Erlang.)

- Also import min/2 and max/2 from the 'erlang' module for the
  same reason.

- Commas in function heads and function calls should be followed
  by one space. Example:

      g(A, B, C) ->
          A + g(B, c).

- Don't use any space after commas in terms and records. Example:

      {ok,[1,2,3]}

- Don't use spaces around '=' in records.

- When you want to match and bind a variable at the same time in a
  function head or case clause, put the pattern before the variable,
  and no spaces around the '='. Example:

       foo(#edge{vs=Va,lf=Lf}=Rec) ->
            ...

- Never use is is_record/2. Match the record directly instead:

       bar(#we{}=We) ->
           ...

  The Erlang compiler usually generate better code if the
  record is matched directly, especially if there are other
  clauses that match other records or data structures.

- Prefer matching out variables from records like this

       foobar(Face, #we{fs=Ftab0}=We) ->
            case gb_trees:get(Face, Ftab0) of
	    	 ......

  instead of using the Rec#record.field syntax

       foobar(Face, We) ->
            case gb_trees:get(Face, We#we.fs) of
	    	 ...

  The Erlang compiler usually generates better code in the former
  case. Especially avoid using the Rec#record.field in guards, as
  the Erlang compiler usually generates terrible code. That is,
  do NOT write:

        bad_idea(We) when We#we.id > 0 ->
            ...

- Spread out record definitions on multiple lines with each field
  name on separate line and comment each field with a comment
  on the same line.

- Atom and function names consisting of several words should be
  written in lowercase and combined with underscores ('_').
  Example: long_atom_name

- Variable names consisting of several words should be written in
  CamelCase, that is, the first letter of each word should be in
  uppercase and the words should be joined together without spaces.
  Example: VariableInCamelCase

- Frequently used variables are better kept short, to avoid having
  to break too many lines. Try to use the existing conventions
  instead of inventing your own. Common names to hold entire
  records are:

  	We to hold a #we{} record
  	St to hold a #st{} record

  Common names for record fields are:

	#we{fs=Ftab,es=Etab,vp=Vtab}

	#edge{vs=Va,ve=Vb,lf=Lf,rf=Rf,
	      ltpr=Ltpr,ltsu=Ltsu,rtpr=Rtpr,rtsu=Rtsu}

  If you have no better names (more specific), use [H|T] for
  matching out the head and tail of a list (there is no reason to
  use longer names such as [Head|Tail] and [First|Rest]).

- When naming variables, atoms, and functions, prefer pronounceable
  names. Avoid names such as "Btn" (short for "Button") and "Mnu"
  (short for "Menu") which were constructed by removing vowels from
  a single word.

  Accepted abbreviations such as HTML is OK to use in variable names,
  as are new abbreviations constructed by taking the first letter of
  several words (e.g. "Lf" which stands for "Left Face").

  Naming is the hardest thing in programming (at least for me) and in
  the end you will just have to use your common sense to come up
  with good names.

- Don't look into abstract/opaque data types. For instance, do NOT
  write like this
  
         ugly_selected_element(#st{sel=[{Id,{1,Element,_,_}}]}=St) ->
             do_something(Id, Element, St);
	 ugly_selected_element(_) ->
	     wings_u:error_msg("Only select a single element.").
  
  to implement a command that only accepts a selection containing
  a single element. Breaking abstractions is not acceptable. You
  will just have to bite the bullet and write:

         selected_element(#st{sel=[{Id,Sel}]}=St) ->
	     case gb_sets:to_list(Sel) of
	         [Element] ->
		     do_something(Id, Element, St);
		 _ ->
		     mul_sel_error()
             end;
	 selected_element(_) -> mul_sel_error().

         mul_sel_error() ->
	     wings_u:error_msg("Only select a single element.").

  Common abstract data types in the standard libraries are
  gb_trees, gb_sets, array, dict, and sets. In Wings, we have
  the wings_va module that must be used for all manipulation of
  vertex attributes.

- Use existing APIs and libraries. Don't write new code to
  manipulate common data structures before doing a serious attempt
  to find out whether there is existing functionality that can
  solve your problem.

- Never use size/1; use either tuple_size/1 or byte_size/1.

- Don't use lists:keysearch/3; use lists:keyfind/3 (introduced
  in R13B) which eliminates the outer '{value,...}' tuple.

- Avoid 'and' and 'or'. They force you to use parenthesis around
  the conditions

        (A =:= B) and (C =:= D)

  Instead, use comma (for 'and') or semi-colon (for 'or') in guards

        f(A, B, C, D) when A =:= B, C =:= D ->
	    ...

  For Boolean expression outside of guards, prefer 'andalso'/'orelse':

        case A =:= B andalso C =:= D of
	    false -> ...'
	    true -> ...
        end

  Only use 'and' or 'or' if it is important that the right-hand side
  is evaluated. For instance, the following expression

        true or length(L) > 1

  will cause an exception if L is not bound to a list, while 

        true orelse length(B) > 1

  will never cause an exception.

  'and' and 'andalso' will in general only behave differently when they
  are nested inside another Boolean expression or in a negated Boolean
  expression. For example, the following expression

         not (false and (length(L) > 42))

  will behave differently from 

         not (false andalso length(L) > 42)

  when L is not bound to a list.

- Only use 'andalso'/'orelse' in guards when ',' and ';' cannot say
  what you want to say. For example, do not write

         foo(A, B) when A =:= 7 orelse B =:= 42 ->
	     ...

  when

         foo(A, B) when A =:= 7; B =:= 42 ->
	     ...

  will do just fine.

- When you need several variable names in order to update a data
  structure, number using "0" as suffix for the first instance.
  Example:

         update(We0) ->
	     We1 = some_update(We0),
	     We2 = some_other_update(We1),
	     We = penultimate_update(We2),
	     final_update(We).

- Note the difference between '==' and '=:=':

         2 == 2.0 returns 'true'.
         2 =:= 2.0 returns 'false'.

  In matching, '=:=' is used implicitly. So given the following
  function definition

         equal(Same, Same) -> true;
         equal(_, _) -> false.

  calling cmp(2, 2) will return 'true' and cmp(2, 2.0) returns
  'false'. So to be consistent with matching, you must use the
  '=:=' operator.

  On the other hand, the '<', '>', '=<', and '>=' operators all
  behave similar to '=='. Therefore, in the following function

         cmp(A, B) when A < B -> less;
         cmp(A, B) when A == B -> equal;
         cmp(A, B when A > B -> greater.

  the operator in the second clause must be '==' and not '=:='.
  If the function was defined like this

         bad_cmp(A, B) when A < B -> less;
         bad_cmp(A, B) when A =:= B -> equal;
         bad_cmp(A, B when A > B -> greater.

  the call bad_cmp(2, 2.0) will cause 'function_clause' exception
  because none of the clauses will match.

  For non-numeric data types, '==' and '=:=' gives the same result,
  but '=:=' is slightly faster.

  To corresponding operators for testing non-equality are called
  '/=' and '=/='.

  I recommend using '=:=' and '=/=' unless there is a specific
  reason not to.