├── .gitignore
├── README.md
├── mix.exs
└── mix.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /deps
3 | /docs
4 | erl_crash.dump
5 | *.ez
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Credo](https://github.com/rrrene/credo)'s Elixir Style Guide [](https://beta.hexfaktor.org/github/rrrene/elixir-style-guide)
2 |
3 | ## Prelude
4 |
5 | There are two reasons for this document to exist:
6 |
7 | - It is my personal style guide and consists both of the way I write Elixir today, but more importantly of things I've seen in the wild and adapted because they make Elixir more readable for open source Alchemists everywhere.
8 | - Secondly, it is the [basis for Credo](https://github.com/rrrene/credo) and reflects the principles promoted by its code analysis.
9 |
10 | Like all of my work, this style guide stands on the shoulders of giants: It is influenced by the Ruby style guides by [bbatsov](https://github.com/bbatsov/ruby-style-guide) and [GitHub](https://github.com/styleguide/ruby) as well as more [public](http://elixir.community/styleguide) [attempts](https://github.com/niftyn8/elixir_style_guide) at Elixir Style Guides.
11 |
12 |
13 |
14 | ## Philosophy
15 |
16 | Contrary to other guides I've seen, this one is not very dogmatic. The overall principles are
17 |
18 | * be consistent in your choices (i.e. apply the same rules everywhere),
19 | * care about the readability of your code (e.g. when in doubt, spread text vertically rather than horizontally),
20 | * and care about easier maintenance (avoid confusing names, etc.).
21 |
22 | This is especially important because we are such a young community. All the **code we put out there is worth its weight in gold** if it is easy to comprehend and invites people to learn and contribute.
23 |
24 |
25 |
26 | ## Contribute
27 |
28 | If you want to add to this document, please submit a pull request or open an issue to discuss specific points.
29 |
30 |
31 | ## The Actual Guide
32 |
33 | ### Code Readability
34 |
35 | *
36 | Use tabs consistently (2 spaces soft-tabs are **preferred**).
37 | [[link](#spaces-indentation)]
38 |
39 | *
40 | Use line-endings consistently (Unix-style line endings are **preferred**, but we should not exclude our brothers and sisters riding the Redmond dragon).
41 | [[link](#line-endings)]
42 |
43 | *
44 | Don't leave trailing white-space at the end of a line.
45 | [[link](#no-trailing-whitespace)]
46 |
47 | *
48 | End each file with a newline (some editors [don't do this by default](https://robots.thoughtbot.com/no-newline-at-end-of-file)).
49 | [[link](#newline-eof)]
50 |
51 | *
52 | Use spaces around operators and after commas.
53 | [[link](#spaces-operators)]
54 |
55 | *
56 | Don't use spaces after `(`, `[`, and `{` or before `}`, `]`, and `)`. This is the **preferred** way, although other styles are possible, as long as they are applied consistently.
57 | [[link](#spaces-braces)]
58 |
59 | ```elixir
60 | # preferred way
61 | Helper.format({1, true, 2}, :my_atom)
62 |
63 | # also okay - carefully choose a style and use it consistently
64 | Helper.format( { 1, true, 2 }, :my_atom )
65 | ```
66 |
67 | *
68 | Keep lines fewer than 80 characters whenever possible, although this is not a strict rule.
69 | [[link](#character-per-line-limit)]
70 |
71 | *
72 | Don't use `;` to separate statements and expressions.
73 | [[link](#semicolon-between-statements)]
74 |
75 | ```elixir
76 | # preferred way
77 | IO.puts "Waiting for:"
78 | IO.inspect object
79 |
80 | # NOT okay
81 | IO.puts "Waiting for:"; IO.inspect object
82 | ```
83 |
84 | *
85 | Don't put a space after `!` to negate an expression.
86 | [[link](#no-space-bang)]
87 |
88 | ```elixir
89 | # preferred way
90 | denied = !allowed?
91 |
92 | # NOT okay
93 | denied = ! allowed?
94 | ```
95 |
96 | *
97 | Group function definitions. Keep the same function with different signatures together without separating blank lines. In all other cases, use blank lines to separate different functions/parts of your module (to maximize readability through "vertical white-space").
98 | [[link](#group-function-definitions)]
99 |
100 | ```elixir
101 | defp find_properties(source_file, config) do
102 | {property_for(source_file, config), source_file}
103 | end
104 |
105 | defp property_for(source_file, _config) do
106 | Enum.map(lines, &tabs_or_spaces/1)
107 | end
108 |
109 | defp tabs_or_spaces({_, "\t" <> line}), do: :tabs
110 | defp tabs_or_spaces({_, " " <> line}), do: :spaces
111 | defp tabs_or_spaces({_, line}), do: nil
112 | ```
113 |
114 | *
115 | Generally use vertical-space to improve readability of sections of your code.
116 | [[link](#vertical-space)]
117 |
118 | ```elixir
119 | # it is preferred to employ a mixture of parentheses, descriptive variable
120 | # names and vertical white space to improve readability
121 |
122 | def run(%SourceFile{} = source_file, params \\ []) do
123 | source_file
124 | |> Helper.find_unused_calls(params, [:String], nil)
125 | |> Enum.reduce([], &add_to_issues/2)
126 | end
127 |
128 | defp add_to_issues(invalid_call, issues) do
129 | {trigger, meta, _} = invalid_call
130 |
131 | issues ++ [issue(meta[:line], trigger, source_file)]
132 | end
133 |
134 | # this function does the same as above, but is less comprehensible
135 |
136 | def run(%SourceFile{} = source_file, params \\ []) do
137 | Helper.find_unused_calls(source_file, params, [:String], nil)
138 | |> Enum.reduce [], fn {_, meta, _} = invalid_call, issues ->
139 | trigger = invalid_call |> Macro.to_string |> String.split("(") |> List.first
140 | issues ++ [issue(meta[:line], trigger, source_file)]
141 | end
142 | end
143 |
144 | ```
145 |
146 | *
147 | It is **preferred** to start pipe chains with a "pure" value rather than a function call.
148 | [[link](#pipe-chains)]
149 |
150 | ```elixir
151 | # preferred way - this is very readable due to the clear flow of data
152 | username
153 | |> String.strip
154 | |> String.downcase
155 |
156 | # also okay - but often slightly less readable
157 | String.strip(username)
158 | |> String.downcase
159 | ```
160 |
161 | *
162 | When assigning to a multi-line call, begin a new line after the `=`. Indent the assigned value's calculation by one level. This is the **preferred** way.
163 | [[link](#multi-line-call)]
164 |
165 | ```elixir
166 | # preferred way
167 | result =
168 | lines
169 | |> Enum.map(&tabs_or_spaces/1)
170 | |> Enum.uniq
171 |
172 | # also okay - align the first assignment and subsequent lines
173 | result = lines
174 | |> Enum.map(&tabs_or_spaces/1)
175 | |> Enum.uniq
176 | ```
177 |
178 | *
179 | Add underscores to large numbers for better readability.
180 | [[link](#underscores-in-numerics)]
181 |
182 | ```elixir
183 | # preferred way - very easy to read
184 | num = 10_000_000
185 |
186 | # NOT okay - how many zeros are there?
187 | num = 10000000
188 | ```
189 |
190 | *
191 | Use `def`, `defp`, and `defmacro` with parentheses when the function takes parameters. Omit the parentheses when the function doesn't accept any parameters. This is the **preferred** way.
192 | [[link](#function-parens)]
193 |
194 | ```elixir
195 | # preferred way - omit parentheses for functions without parameters
196 | def time do
197 | # ...
198 | end
199 |
200 | # use parentheses if parameters are present
201 | def convert(x, y) do
202 | # ...
203 | end
204 | ```
205 |
206 | *
207 | Most of the time when calling functions that take parameters, it is **preferred** to use parentheses.
208 | [[link](#function-calling-parens)]
209 |
210 | ```elixir
211 | # preferred way - the more boring forms are preferred since it's easier to see what goes where
212 | Enum.reduce(1..100, 0, &(&1 + &2))
213 |
214 | Enum.reduce(1..100, 0, fn(x, acc) ->
215 | x + acc
216 | end)
217 |
218 | # also okay - carefully choose a style and use it consistently
219 | Enum.reduce 1..100, 0, & &1 + &2
220 |
221 | Enum.reduce 1..100, 0, fn x, acc ->
222 | x + acc
223 | end
224 |
225 | ```
226 |
227 | *
228 | For macros we see the contrary behaviour. The **preferred** way is to not use parentheses.
229 | [[link](#macro-parens)]
230 |
231 | ```elixir
232 | # preferred way
233 | defmodule MyApp.Service.TwitterAPI do
234 | use MyApp.Service, social: true
235 |
236 | alias MyApp.Service.Helper, as: H
237 | end
238 | ```
239 |
240 | *
241 | Conclusively, never use parentheses around the condition of `if` or `unless` (since they are macros as well).
242 | [[link](#conditional-parens)]
243 |
244 | ```elixir
245 | # preferred way
246 | if valid?(username) do
247 | # ...
248 | end
249 |
250 | # NOT okay
251 | if( valid?(username) ) do
252 | # ...
253 | end
254 | ```
255 |
256 | #### Naming
257 |
258 | *
259 | Use CamelCase for module names. It is **preferred** to keep acronyms like HTTP, XML uppercase.
260 | [[link](#camelcase-modules)]
261 |
262 | ```elixir
263 | # preferred way
264 | defmodule MyApp.HTTPService do
265 | end
266 |
267 | # also okay - carefully choose a style and use it consistently
268 | defmodule MyApp.HttpService do
269 | end
270 | ```
271 |
272 | *
273 | Use snake_case for module attribute, function, macro and variable names.
274 | [[link](#snake-case-attributes-functions-macros-vars)]
275 |
276 | ```elixir
277 | # preferred way
278 | defmodule MyApp.HTTPService do
279 | @some_setting :my_value
280 |
281 | def my_function(param_value) do
282 | variable_value1 = "test"
283 | end
284 | end
285 |
286 | # NOT okay
287 | defmodule MyApp.HTTPService do
288 | @someSetting :my_value
289 |
290 | def myFunction(paramValue) do
291 | variableValue1 = "test"
292 | end
293 | end
294 | ```
295 |
296 | *
297 | Exception names should have a common prefix or suffix. While this can be anything you like, esp. for small libraries, a common choice seems to have all of them end in `Error`.
298 | [[link](#exception-naming)]
299 |
300 | ```elixir
301 | # preferred way - common suffix Error
302 | defmodule BadHTTPHeaderError do
303 | defexception [:message]
304 | end
305 |
306 | defmodule HTTPRequestError do
307 | defexception [:message]
308 | end
309 |
310 | # also okay - consistent prefix Invalid
311 | defmodule InvalidHTTPHeader do
312 | defexception [:message]
313 | end
314 |
315 | defmodule InvalidUserRequest do
316 | defexception [:message]
317 | end
318 |
319 | # bad - there is no common naming scheme for exceptions
320 | defmodule InvalidHeader do
321 | defexception [:message]
322 | end
323 |
324 | defmodule RequestFailed do
325 | defexception [:message]
326 | end
327 | ```
328 |
329 | *
330 | Predicate functions/macros should return a boolean value.
331 | [[link](#predicates)]
332 |
333 | For functions, they should end in a question mark.
334 |
335 | ```elixir
336 | # preferred way
337 | def valid?(username) do
338 | # ...
339 | end
340 |
341 | # NOT okay
342 | def is_valid?(username) do
343 | # ...
344 | end
345 | ```
346 |
347 | For guard-safe macros they should have the prefix `is_` and not end in a question mark.
348 |
349 | ```elixir
350 | # preferred way
351 | defmacro is_valid(username) do
352 | # ...
353 | end
354 |
355 | # NOT okay
356 | defmacro valid?(username) do
357 | # ...
358 | end
359 | ```
360 |
361 |
362 |
363 | #### Sigils
364 |
365 | *
366 | Use sigils where it makes sense, but don't use them in a dogmatic way.
367 | [[link](#sigils)]
368 |
369 | Example: Don't automatically use `~S` just because there is *one* `"` in your string, but start using it when you would have to escape a lot of double-quotes.
370 |
371 | ```elixir
372 | # preferred way - use normal quotes even if one has to be escaped
373 | legend = "single quote ('), double quote (\")"
374 |
375 | # use sigils when you would have to escape several quotes otherwise
376 | html = ~S(Homepage)
377 |
378 | # also okay, but not preferred - important: choose a common sigil and stick with it
379 | # avoid using ~S{} in one place while using ~S(), ~S[] and ~S<> in others
380 | legend = ~S{single quote ('), double quote (")}
381 | html = "Homepage"
382 | ```
383 |
384 | #### Regular Expressions
385 |
386 | *
387 | Use `~r//` as your "go-to sigil" when it comes to Regexes as they are the easiest to read for people new to Elixir. That said, feel free to use other `~r` sigils when you have several slashes in your expression.
388 | [[link](#regex-sigils)]
389 |
390 | ```elixir
391 | # preferred way - use slashes because they are familiar regex delimiters
392 | regex = ~r/\d+/
393 |
394 | # use sigils when you would have to escape several quotes otherwise
395 | regex = ~r{http://elixir-lang.org/getting-started/mix-otp/(.+).html}
396 | ```
397 |
398 | *
399 | Be careful with `^` and `$` as they match start/end of line, not string endings. If you want to match the whole string use: `\A` and `\z`.
400 | [[link](#caret-and-dollar-regex)]
401 |
402 |
403 |
404 |
405 | ### Documentation
406 |
407 | *
408 | First, try to see documentation in a positive light. It is not a chore that you put off for as long as possible (forever). At some point in your project, starting to document parts of your project is an opportunity to communicate important things with future-maintainers, potential users of your API and even your future self.
409 | [[link](#documentation)]
410 |
411 | *
412 | When that point in time comes, every module and function should either be documented or marked via `@moduledoc false`/`@doc false` to indicate that there is no intent to document the object in question.
413 | [[link](#doc-false)]
414 |
415 | *
416 | Use ExDoc for this, it's great.
417 | [[link](#exdoc)]
418 |
419 | *
420 | As for style, put an empty line after `@moduledoc`. Don't put an empty line between `@doc` and its function/macro definition.
421 | [[link](#doc-style)]
422 |
423 | ```elixir
424 | defmodule MyApp.HTTPService do
425 | @moduledoc false
426 |
427 | @doc "Sends a POST request to the given `url`."
428 | def post(url) do
429 | # ...
430 | end
431 | end
432 | ```
433 |
434 | *
435 | Although Elixir favors `@moduledoc` and `@doc` as first-class citizens, don't be afraid to communicate via normal code comments as well. But remember: don't use this to explain bad code!
436 | [[link](#doc-comments)]
437 |
438 | ```elixir
439 | # preferred way - provide useful additional information
440 | defmodule Credo.Issue do
441 | defstruct category: nil,
442 | message: nil,
443 | filename: nil,
444 | line_no: nil,
445 | column: nil,
446 | trigger: nil, # optional: the call that triggered the issue
447 | metadata: [], # optional: filled in by the failing check
448 | end
449 |
450 | # NOT okay - "explaining" confusing code and ambiguous names
451 | defmodule AbstractCredoIssueInterfaceFactory do
452 | def build(input, params \\ []) do
453 | Helper.find_values(input, params) # input is either a username or pid
454 | |> Enum.reduce %{}, fn {_, meta, _} = value, list ->
455 | if valid?(value) do
456 | case Map.get(list, :action) do # remember: list is a map!!!!111
457 | nil -> nil # why does this break sometimes?
458 | val -> Map.put(list, val, true)
459 | end
460 | else
461 | list
462 | end
463 | end
464 | end
465 | end
466 | ```
467 |
468 | *
469 | If necessary, put longer, more descriptive comments on their own line rather than at the end of a line of code.
470 | [[link](#doc-comments-on-own-lines)]
471 |
472 |
473 |
474 | ### Refactoring Opportunities
475 |
476 |
477 | *
478 | Never nest `if`, `unless`, and `case` more than 1 time. If your logic demands it, spread it over multiple functions.
479 | [[link](#no-nested-conditionals)]
480 |
481 | ```elixir
482 | # preferred way
483 | defp perform_task(false, hash, config) do
484 | nil
485 | end
486 | defp perform_task(true, hash, config) do
487 | hash
488 | |> Map.get(:action)
489 | |> perform_action(config)
490 | end
491 |
492 | defp perform_action(nil, _config) do
493 | nil
494 | end
495 | defp perform_action(:create, _config) do
496 | # ...
497 | end
498 | defp perform_action(:delete, config) do
499 | if config[:id] do
500 | # ...
501 | else
502 | # ...
503 | end
504 | end
505 |
506 | # NOT okay - rule of thumb: it starts to hurt at 3 levels of nesting
507 | defp perform_task(valid, hash, config) do
508 | if valid do
509 | case Map.get(hash, :action) do
510 | :create ->
511 | # ...
512 | :delete ->
513 | if sid do # <-- we reach three levels of nesting here :(
514 | # ...
515 | else
516 | # ...
517 | end
518 | nil ->
519 | nil
520 | end
521 | end
522 | end
523 |
524 | ```
525 |
526 | *
527 | Never use `unless` with else. Rewrite these with `if`, putting the positive case first.
528 | [[link](#no-unless-with-else)]
529 |
530 | ```elixir
531 | # without an else block:
532 | unless allowed? do
533 | raise "Not allowed!"
534 | end
535 |
536 | # preferred way to "add" an `else` block here: rewrite using `if`
537 | if allowed? do
538 | proceed_as_planned
539 | else
540 | raise "Not allowed!"
541 | end
542 |
543 | # NOT okay
544 | unless allowed? do
545 | raise "Not allowed!"
546 | else
547 | proceed_as_planned
548 | end
549 | ```
550 |
551 | *
552 | Never use `unless` with a negated expression as condition. Rewrite with `if`.
553 | [[link](#avoid-double-negations)]
554 |
555 | ```elixir
556 | # preferred way
557 | if allowed? do
558 | proceed_as_planned
559 | end
560 |
561 | # NOT okay - rewrite using `if`
562 | unless !allowed? do
563 | proceed_as_planned
564 | end
565 | ```
566 |
567 | *
568 | Always use `__MODULE__` when referencing the current module.
569 | [[link](#reference-current-module)]
570 |
571 |
572 | ### Software Design
573 |
574 | *
575 | Use `FIXME:` comments to mark issues/bugs inside your code.
576 | [[link](#fixme)]
577 |
578 | ```elixir
579 | defmodule MyApp do
580 | # FIXME: this breaks for x > 1000
581 | def calculate(x) do
582 | # ...
583 | end
584 | end
585 | ```
586 |
587 | *
588 | Use `TODO:` comments to plan changes to your code.
589 | [[link](#todo)]
590 |
591 | ```elixir
592 | defmodule MyApp do
593 | # TODO: rename into something more clear
594 | def generic_function_name do
595 | # ...
596 | end
597 | end
598 | ```
599 |
600 | This way tools have a chance to find and report both `FIXME:` and `TODO:` comments.
601 |
602 | *
603 | When developing applications, try to alias all used modules. This improves readability and makes it easier to reason about the dependencies of a module inside your project. There are obvious exceptions for modules from Elixir's stdlib (e.g. `IO.ANSI`) or if your submodule has a name identical to an existing name (e.g. don't alias `YourProject.List` because that would override `List`). Like most other points in this guide, this is just a suggestion, not a strict rule.
604 | [[link](#alias-modules)]
605 |
606 | ```elixir
607 | # While this is completely fine:
608 |
609 | defmodule Test do
610 | def something do
611 | MyApp.External.TwitterAPI.search(...)
612 | end
613 | end
614 |
615 | # ... you might want to refactor it to look like this:
616 |
617 | defmodule Test do
618 | alias MyApp.External.TwitterAPI
619 |
620 | def something do
621 | TwitterAPI.search(...)
622 | end
623 | end
624 | ```
625 |
626 | The thinking behind this is that you can see the dependencies of your module at a glance. So if you are attempting to build a medium to large project, **this can help you to get your boundaries/layers/contracts right**.
627 |
628 |
629 |
630 | ### Pitfalls
631 |
632 | *
633 | Never leave a call to `IEx.pry` in production code.
634 | [[link](#iex-pry)]
635 |
636 | *
637 | Be wary of calls to `IO.inspect` in production code. If you want to actually log useful information for later debugging, use a combination of `Logger` and `&inspect/1` instead.
638 | [[link](#io-inspect)]
639 |
640 | *
641 | Conditionals should never contain an expression that always evaluates to the same value (such as `true`, `false`, `x == x`). They are most likely leftovers from a debugging session.
642 | [[link](#debugging-conditionals)]
643 |
644 | *
645 | Be wary of naming variables and functions the same as functions defined in `Kernel`, especially in cases where the function has arity 0.
646 | [[link](#kernel-functions)]
647 |
648 | *
649 | Be wary of naming modules the same as modules in the stdlib. Sometimes `YourProject.DataTypeString` is a less error-prone choice as the seemingly cleaner `YourProject.DataType.String` because aliasing the latter in a module makes the *normal* `String` module unavailable.
650 | [[link](#stdlib-modules)]
651 |
652 |
653 |
654 | ### Above all else
655 |
656 | Follow your instincts. Write coherent code by applying a consistent style.
657 |
658 |
659 |
660 | ## License
661 |
662 | This work is licensed under [the CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0).
663 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ElixirStyleGuide.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :elixir_style_guide,
6 | version: "0.0.1",
7 | elixir: "~> 1.2",
8 | build_embedded: Mix.env == :prod,
9 | start_permanent: Mix.env == :prod,
10 | deps: deps]
11 | end
12 |
13 | # Configuration for the OTP application
14 | #
15 | # Type "mix help compile.app" for more information
16 | def application do
17 | [applications: [:logger]]
18 | end
19 |
20 | # Dependencies can be Hex packages:
21 | #
22 | # {:mydep, "~> 0.3.0"}
23 | #
24 | # Or git/path repositories:
25 | #
26 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
27 | #
28 | # Type "mix help deps" for more examples and options
29 | defp deps do
30 | [{ :obelisk, "~> 0.7" },
31 | { :yamerl, github: "yakaz/yamerl"}]
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"anubis": {:hex, :anubis, "0.1.0"},
2 | "calliope": {:hex, :calliope, "0.3.0"},
3 | "chronos": {:hex, :chronos, "0.3.9"},
4 | "cowboy": {:hex, :cowboy, "1.0.4"},
5 | "cowlib": {:hex, :cowlib, "1.0.2"},
6 | "earmark": {:hex, :earmark, "0.1.19"},
7 | "meck": {:hex, :meck, "0.8.4"},
8 | "mock": {:hex, :mock, "0.1.1"},
9 | "obelisk": {:hex, :obelisk, "0.10.0"},
10 | "plug": {:hex, :plug, "0.11.3"},
11 | "ranch": {:hex, :ranch, "1.2.1"},
12 | "rss": {:hex, :rss, "0.2.1"},
13 | "yamerl": {:git, "https://github.com/yakaz/yamerl.git", "ae810a808817d9482b4628ae3e20d746e3729fe0", []}}
14 |
--------------------------------------------------------------------------------