├── .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 [![Deps Status](https://beta.hexfaktor.org/badge/all/github/rrrene/elixir-style-guide.svg)](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 | --------------------------------------------------------------------------------