Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
792d4cc631 | ||
|
|
f8016bca48 | ||
|
|
82818f7126 | ||
|
|
5c8f9aac64 | ||
|
|
1a8bad1bf2 | ||
|
|
4b795871b6 | ||
|
|
3036401c7c | ||
|
|
8bbba572de | ||
|
|
968b51319e | ||
|
|
900f25f832 |
+3
-2
@@ -57,13 +57,13 @@ A huge thank you to Vinícius Muller for working on the new diagnostics.
|
||||
|
||||
Elixir's Getting Started guided has been made part of the Elixir repository and incorporated into ExDoc. This was an opportunity to revisit and unify all official guides and references.
|
||||
|
||||
We have also incorporated and extended the work on [Understanding Code Smells in Elixir Functional Language](https://github.com/lucasvegi/Elixir-Code-Smells/blob/main/etc/2023-emse-code-smells-elixir.pdf), by Lucas Vegi and Marco Tulio Valente, from [ASERG/DCC/UFMG](http://aserg.labsoft.dcc.ufmg.br/), into the official document in the form of anti-patterns. The anti-patterns are divided into four categories: code-related, design-related, process-related, and meta-programming. Our goal is to give all developers with both positive and negative examples of Elixir code, with context and examples on how to improve their codebases.
|
||||
We have also incorporated and extended the work on [Understanding Code Smells in Elixir Functional Language](https://github.com/lucasvegi/Elixir-Code-Smells/blob/main/etc/2023-emse-code-smells-elixir.pdf), by Lucas Vegi and Marco Tulio Valente, from [ASERG/DCC/UFMG](http://aserg.labsoft.dcc.ufmg.br/), into the official document in the form of anti-patterns. The anti-patterns are divided into four categories: code-related, design-related, process-related, and meta-programming. Our goal is to give all developers examples of potential anti-patterns, with context and examples on how to improve their codebases.
|
||||
|
||||
Another [ExDoc](https://github.com/elixir-lang/ex_doc) feature we have incorporated in this release is the addition of cheatsheets, starting with [a cheatsheet for the Enum module](https://hexdocs.pm/elixir/main/enum-cheat.html). If you would like to contribute future cheatsheets to Elixir itself, feel free to start a discussion with an issue.
|
||||
|
||||
Finally, we have started enriching our documentation with [Mermaid.js](https://mermaid.js.org/) diagrams. You can find examples in the [GenServer](https://hexdocs.pm/elixir/main/GenServer.html) and [Supervisor](https://hexdocs.pm/elixir/main/Supervisor.html) docs.
|
||||
|
||||
## v1.16.0-dev
|
||||
## v1.16.0-rc.0 (2023-10-31)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -76,6 +76,7 @@ Finally, we have started enriching our documentation with [Mermaid.js](https://m
|
||||
* [Code] Automatically include columns in parsing options
|
||||
* [Code] Introduce `MismatchedDelimiterError` for handling mismatched delimiter exceptions
|
||||
* [Code.Fragment] Handle anonymous calls in fragments
|
||||
* [Code.Formatter] Trim trailing whitespace on heredocs with `\r\n`
|
||||
* [Kernel] Suggest module names based on suffix and casing errors when the module does not exist in `UndefinedFunctionError`
|
||||
* [Kernel.ParallelCompiler] Introduce `Kernel.ParallelCompiler.pmap/2` to compile multiple additional entries in parallel
|
||||
* [Kernel.SpecialForms] Warn if `True`/`False`/`Nil` are used as aliases and there is no such alias
|
||||
|
||||
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
|
||||
TEST_FILES ?= "*_test.exs"
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
CANONICAL := main/
|
||||
# CANONICAL := main/
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL_MAKE := if [ -n "$(ERLC_OPTS)" ]; then ERL_COMPILER_OPTIONS=$(ERLC_OPTS) erl -make; else erl -make; fi
|
||||
|
||||
+2
-3
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.16 | Development
|
||||
1.15 | Bug fixes and security patches
|
||||
1.16 | Bug fixes and security patches
|
||||
1.15 | Security patches only
|
||||
1.14 | Security patches only
|
||||
1.13 | Security patches only
|
||||
1.12 | Security patches only
|
||||
1.11 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.16.0-dev
|
||||
ELIXIR_VERSION=1.16.0-rc.0
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.16.0-dev
|
||||
set ELIXIR_VERSION=1.16.0-rc.0
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -475,7 +475,7 @@ defmodule GenServer do
|
||||
guide provides a tutorial-like introduction. The documentation and links
|
||||
in Erlang can also provide extra insight.
|
||||
|
||||
* [GenServer - Elixir's Getting Started Guide](https://elixir-lang.org/getting-started/mix-otp/genserver.html)
|
||||
* [GenServer - Elixir's Getting Started Guide](genservers.md)
|
||||
* [`:gen_server` module documentation](`:gen_server`)
|
||||
* [gen_server Behaviour - OTP Design Principles](https://www.erlang.org/doc/design_principles/gen_server_concepts.html)
|
||||
* [Clients and Servers - Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/clients-and-servers)
|
||||
|
||||
@@ -504,7 +504,7 @@ defmodule Process do
|
||||
If the process is already dead when calling `Process.monitor/1`, a
|
||||
`:DOWN` message is delivered immediately.
|
||||
|
||||
See ["The need for monitoring"](https://elixir-lang.org/getting-started/mix-otp/genserver.html#the-need-for-monitoring)
|
||||
See ["The need for monitoring"](genservers.md#the-need-for-monitoring)
|
||||
for an example. See `:erlang.monitor/2` for more information.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -27,8 +27,8 @@ defmodule Registry do
|
||||
`Registry.start_link/1`, it can be used to register and access named
|
||||
processes using the `{:via, Registry, {registry, key}}` tuple:
|
||||
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
|
||||
name = {:via, Registry, {Registry.ViaTest, "agent"}}
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
|
||||
name = {:via, Registry, {MyApp.Registry, "agent"}}
|
||||
{:ok, _} = Agent.start_link(fn -> 0 end, name: name)
|
||||
Agent.get(name, & &1)
|
||||
#=> 0
|
||||
@@ -39,22 +39,22 @@ defmodule Registry do
|
||||
In the previous example, we were not interested in associating a value to the
|
||||
process:
|
||||
|
||||
Registry.lookup(Registry.ViaTest, "agent")
|
||||
Registry.lookup(MyApp.Registry, "agent")
|
||||
#=> [{self(), nil}]
|
||||
|
||||
However, in some cases it may be desired to associate a value to the process
|
||||
using the alternate `{:via, Registry, {registry, key, value}}` tuple:
|
||||
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: Registry.ViaTest)
|
||||
name = {:via, Registry, {Registry.ViaTest, "agent", :hello}}
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: MyApp.Registry)
|
||||
name = {:via, Registry, {MyApp.Registry, "agent", :hello}}
|
||||
{:ok, agent_pid} = Agent.start_link(fn -> 0 end, name: name)
|
||||
Registry.lookup(Registry.ViaTest, "agent")
|
||||
Registry.lookup(MyApp.Registry, "agent")
|
||||
#=> [{agent_pid, :hello}]
|
||||
|
||||
To this point, we have been starting `Registry` using `start_link/1`.
|
||||
Typically the registry is started as part of a supervision tree though:
|
||||
|
||||
{Registry, keys: :unique, name: Registry.ViaTest}
|
||||
{Registry, keys: :unique, name: MyApp.Registry}
|
||||
|
||||
Only registries with unique keys can be used in `:via`. If the name is
|
||||
already taken, the case-specific `start_link` function (`Agent.start_link/2`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Code-related anti-patterns
|
||||
|
||||
This document outlines anti-patterns related to your code and particular Elixir idioms and features.
|
||||
This document outlines potential anti-patterns related to your code and particular Elixir idioms and features.
|
||||
|
||||
## Comments
|
||||
|
||||
@@ -34,7 +34,8 @@ You could refactor the code above like this:
|
||||
@five_min_in_seconds 60 * 5
|
||||
|
||||
defp unix_five_min_from_now do
|
||||
unix_now = DateTime.to_unix(DateTime.utc_now(), :second)
|
||||
now = DateTime.utc_now()
|
||||
unix_now = DateTime.to_unix(now, :second)
|
||||
unix_now + @five_min_in_seconds
|
||||
end
|
||||
```
|
||||
@@ -45,75 +46,6 @@ We removed the unnecessary comments. We also added a `@five_min_in_seconds` modu
|
||||
|
||||
Elixir makes a clear distinction between **documentation** and code comments. The language has built-in first-class support for documentation through `@doc`, `@moduledoc`, and more. See the ["Writing documentation"](../getting-started/writing-documentation.md) guide for more information.
|
||||
|
||||
## Complex branching
|
||||
|
||||
#### Problem
|
||||
|
||||
When a function assumes the responsibility of handling multiple errors alone, it can increase its cyclomatic complexity (metric of control-flow) and become incomprehensible. This situation can configure a specific instance of "Long function", a traditional anti-pattern, but has implications of its own. Under these circumstances, this function could get very confusing, difficult to maintain and test, and therefore bug-proneness.
|
||||
|
||||
#### Example
|
||||
|
||||
An example of this anti-pattern is when a function uses the `case` control-flow structure or other similar constructs (for example, `cond` or `receive`) to handle variations of a return type. This practice can make the function more complex, long, and difficult to understand, as shown next.
|
||||
|
||||
```elixir
|
||||
def get_customer(customer_id) do
|
||||
case SomeHTTPClient.get("/customers/#{customer_id}") do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
case Jason.decode(body) do
|
||||
{:ok, decoded} ->
|
||||
%{
|
||||
"first_name" => first_name,
|
||||
"last_name" => last_name,
|
||||
"company" => company
|
||||
} = decoded
|
||||
|
||||
customer =
|
||||
%Customer{
|
||||
id: customer_id,
|
||||
name: "#{first_name} #{last_name}",
|
||||
company: company
|
||||
}
|
||||
|
||||
{:ok, customer}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, "invalid response body"}
|
||||
end
|
||||
|
||||
{:error, %{status: status, body: body}} ->
|
||||
case Jason.decode(body) do
|
||||
%{"error" => message} when is_binary(message) ->
|
||||
{:error, message}
|
||||
|
||||
%{} ->
|
||||
{:error, "invalid response with status #{status}"}
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The code above is complex because the `case` clauses are long and often have their own branching logic in them. With the clauses spread out, it is hard to understand what each clause does individually and it is hard to see all of the different scenarios your code pattern matches on.
|
||||
|
||||
#### Refactoring
|
||||
|
||||
As shown below, in this situation, instead of concentrating all error handling within the same function, creating complex branches, it is better to delegate each branch to a different private function. In this way, the code will be cleaner and more readable.
|
||||
|
||||
```elixir
|
||||
def get_customer(customer_id) do
|
||||
case SomeHTTPClient.get("/customers/#{customer_id}") do
|
||||
{:ok, %{status: 200, body: body}} ->
|
||||
http_customer_to_struct(customer_id, body)
|
||||
|
||||
{:error, %{status: status, body: body}} ->
|
||||
http_error(status, body)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Both `http_customer_to_struct(customer_id, body)` and `http_error(status, body)` above contain the previous branches refactored into private functions.
|
||||
|
||||
It is worth noting that this refactoring is trivial to perform in Elixir because clauses cannot define variables or otherwise affect their parent scope. Therefore, extracting any clause or branch to a private function is a matter of gathering all variables used in that branch and passing them as arguments to the new function.
|
||||
|
||||
## Complex `else` clauses in `with`
|
||||
|
||||
#### Problem
|
||||
@@ -169,11 +101,11 @@ end
|
||||
|
||||
#### Problem
|
||||
|
||||
When we use multi-clause functions, it is possible to extract values in the clauses for further usage and for pattern matching/guard checking. This extraction itself does not represent an anti-pattern, but when you have too many clauses or too many arguments, it becomes hard to know which extracted parts are used for pattern/guards and what is used only inside the function body. This anti-pattern is related to [Unrelated multi-clause function](design-anti-patterns.md#unrelated-multi-clause-function), but with implications of its own. It impairs the code readability in a different way.
|
||||
When we use multi-clause functions, it is possible to extract values in the clauses for further usage and for pattern matching/guard checking. This extraction itself does not represent an anti-pattern, but when you have *extractions made across several clauses and several arguments of the same function*, it becomes hard to know which extracted parts are used for pattern/guards and what is used only inside the function body. This anti-pattern is related to [Unrelated multi-clause function](design-anti-patterns.md#unrelated-multi-clause-function), but with implications of its own. It impairs the code readability in a different way.
|
||||
|
||||
#### Example
|
||||
|
||||
The multi-clause function `drive/1` is extracting fields of an `%User{}` struct for usage in the clause expression (`age`) and for usage in the function body (`name`). Ideally, a function should not mix pattern matching extractions for usage in its guard expressions and also in its body.
|
||||
The multi-clause function `drive/1` is extracting fields of an `%User{}` struct for usage in the clause expression (`age`) and for usage in the function body (`name`):
|
||||
|
||||
```elixir
|
||||
def drive(%User{name: name, age: age}) when age >= 18 do
|
||||
@@ -185,7 +117,7 @@ def drive(%User{name: name, age: age}) when age < 18 do
|
||||
end
|
||||
```
|
||||
|
||||
While the example is small and looks like a clear code, try to imagine a situation where `drive/1` was more complex, having many more clauses, arguments, and extractions.
|
||||
While the example above is small and does not configure an anti-pattern, it is an example of mixed extraction and pattern matching. A situation where `drive/1` was more complex, having many more clauses, arguments, and extractions, would make it hard to know at a glance which variables are used for pattern/guards and which ones are not.
|
||||
|
||||
#### Refactoring
|
||||
|
||||
@@ -363,11 +295,13 @@ defmodule PlugAuth do
|
||||
end
|
||||
```
|
||||
|
||||
#### Additional remarks
|
||||
|
||||
There are few known exceptions to this anti-pattern:
|
||||
|
||||
* [Protocol implementations](`Kernel.defimpl/2`) are, by design, defined under the protocol namespace
|
||||
|
||||
* [Custom Mix tasks](`Mix.Task`) are always defined under the `Mix.Tasks` namespace, such as `Mix.Tasks.PlugAuth`
|
||||
* In some scenarios, the namespace owner may allow exceptions to this rule. For example, in Elixir itself, you defined [custom Mix tasks](`Mix.Task`) by placing them under the `Mix.Tasks` namespace, such as `Mix.Tasks.PlugAuth`
|
||||
|
||||
* If you are the maintainer for both `plug` and `plug_auth`, then you may allow `plug_auth` to define modules with the `Plug` namespace, such as `Plug.Auth`. However, you are responsible for avoiding or managing any conflicts that may arise in the future
|
||||
|
||||
@@ -436,7 +370,7 @@ iex> Graphics.plot(point_3d)
|
||||
|
||||
Overall, the usage of `map.key` and `map[:key]` encode important information about your data structure, allowing developers to be clear about their intent. See both `Map` and `Access` module documentation for more information and examples.
|
||||
|
||||
Another alternative to refactor this anti-pattern is to use pattern matching:
|
||||
An even simpler alternative to refactor this anti-pattern is to use pattern matching:
|
||||
|
||||
```elixir
|
||||
defmodule Graphics do
|
||||
@@ -461,6 +395,8 @@ iex> Graphics.plot(point_3d)
|
||||
{5, 6, nil}
|
||||
```
|
||||
|
||||
Pattern-matching is specially useful when matching over multiple keys at once and also when you want to match and assert on the values of a map.
|
||||
|
||||
Another alternative is to use structs. By default, structs only support static access to its fields:
|
||||
|
||||
```elixir
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Design-related anti-patterns
|
||||
|
||||
This document outlines anti-patterns related to your modules, functions, and the role they
|
||||
This document outlines potential anti-patterns related to your modules, functions, and the role they
|
||||
play within a codebase.
|
||||
|
||||
## Alternative return types
|
||||
@@ -126,7 +126,7 @@ Remember booleans are internally represented as atoms. Therefore there is no per
|
||||
|
||||
#### Problem
|
||||
|
||||
This anti-pattern refers to code that uses exceptions for control flow. Exception handling itself does not represent an anti-pattern, but developers must prefer to use `case` and pattern matching to change the flow of their code, instead of `try/rescue`. In turn, library authors should provide developers with APIs to handle errors without relying on exception handling. When developers have no freedom to decide if an error is exceptional or not, this is considered an anti-pattern.
|
||||
This anti-pattern refers to code that uses `Exception`s for control flow. Exception handling itself does not represent an anti-pattern, but developers must prefer to use `case` and pattern matching to change the flow of their code, instead of `try/rescue`. In turn, library authors should provide developers with APIs to handle errors without relying on exception handling. When developers have no freedom to decide if an error is exceptional or not, this is considered an anti-pattern.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -186,63 +186,11 @@ end
|
||||
|
||||
A common practice followed by the community is to make the non-raising version return `{:ok, result}` or `{:error, Exception.t}`. For example, an HTTP client may return `{:ok, %HTTP.Response{}}` on success cases and `{:error, %HTTP.Error{}}` for failures, where `HTTP.Error` is [implemented as an exception](`Kernel.defexception/1`). This makes it convenient for anyone to raise an exception by simply calling `Kernel.raise/1`.
|
||||
|
||||
## Feature envy
|
||||
|
||||
#### Problem
|
||||
|
||||
This anti-pattern occurs when a function accesses more data or calls more functions from another module than from its own. The presence of this anti-pattern can make a module less cohesive and increase code coupling.
|
||||
|
||||
#### Example
|
||||
|
||||
In the following code, all the data used in the `calculate_total_item/1` function of the module `Order` comes from the `OrderItem` module. This increases coupling and decreases code cohesion unnecessarily.
|
||||
|
||||
```elixir
|
||||
defmodule Order do
|
||||
# Some functions...
|
||||
|
||||
def calculate_total_item(id) do
|
||||
item = OrderItem.find_item(id)
|
||||
total = (item.price + item.taxes) * item.amount
|
||||
|
||||
if discount = OrderItem.find_discount(item) do
|
||||
total - total * discount
|
||||
else
|
||||
total
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Refactoring
|
||||
|
||||
To remove this anti-pattern we can move `calculate_total_item/1` to `OrderItem`, decreasing coupling:
|
||||
|
||||
```elixir
|
||||
defmodule OrderItem do
|
||||
def find_item(id)
|
||||
def find_discount(item)
|
||||
|
||||
def calculate_total_item(id) do # <= function moved from Order!
|
||||
item = find_item(id)
|
||||
total = (item.price + item.taxes) * item.amount
|
||||
discount = find_discount(item)
|
||||
|
||||
unless is_nil(discount) do
|
||||
total - total * discount
|
||||
else
|
||||
total
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This refactoring is only possible when you own both modules. If the module you are invoking belongs to another application, then it is not possible to add new functions to it, and your only option is to define an additional module that augments the third-party module.
|
||||
|
||||
## Primitive obsession
|
||||
|
||||
#### Problem
|
||||
|
||||
This anti-pattern happens when Elixir basic types (for example, *integer*, *float*, and *string*) are abusively used in function parameters and code variables, rather than creating specific composite data types (for example, *tuples*, *maps*, and *structs*) that can better represent a domain.
|
||||
This anti-pattern happens when Elixir basic types (for example, *integer*, *float*, and *string*) are excessively used to carry structured information, rather than creating specific composite data types (for example, *tuples*, *maps*, and *structs*) that can better represent a domain.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -250,17 +198,23 @@ An example of this anti-pattern is the use of a single *string* to represent an
|
||||
|
||||
```elixir
|
||||
defmodule MyApp do
|
||||
def process_address(address) when is_binary(address) do
|
||||
# Do something with address...
|
||||
def extract_postal_code(address) when is_binary(address) do
|
||||
# Extract postal code with address...
|
||||
end
|
||||
|
||||
def fill_in_country(address) when is_binary(address) do
|
||||
# Fill in missing country...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
While you may receive the `address` as a string from a database, web request, or a third-party, if you find yourself frequently manipulating or extracting information from the string, it is a good indicator you should convert the address into structured data:
|
||||
|
||||
Another example of this anti-pattern is using floating numbers to model money and currency, when [richer data structures should be preferred](https://hexdocs.pm/ex_money/).
|
||||
|
||||
#### Refactoring
|
||||
|
||||
Possible solutions to this anti-pattern is to use maps or structs to model our address. The example below creates an `Address` struct, better representing this domain through a composite type. Additionally, we can modify the `process_address/1` function to accept a parameter of type `Address` instead of a *string*. With this modification, we can extract each field of this composite type individually when needed.
|
||||
Possible solutions to this anti-pattern is to use maps or structs to model our address. The example below creates an `Address` struct, better representing this domain through a composite type. Additionally, we introduce a `parse/1` function, that converts the string into an `Address`, which will simplify the logic of remainng functions. With this modification, we can extract each field of this composite type individually when needed.
|
||||
|
||||
```elixir
|
||||
defmodule Address do
|
||||
@@ -270,8 +224,16 @@ end
|
||||
|
||||
```elixir
|
||||
defmodule MyApp do
|
||||
def process_address(%Address{} = address) do
|
||||
# Do something with address...
|
||||
def parse(address) when is_binary(address) do
|
||||
# Returns %Address{}
|
||||
end
|
||||
|
||||
def extract_postal_code(%Address{} = address) do
|
||||
# Extract postal code with address...
|
||||
end
|
||||
|
||||
def fill_in_country(%Address{} = address) do
|
||||
# Fill in missing country...
|
||||
end
|
||||
end
|
||||
```
|
||||
@@ -336,13 +298,13 @@ The following arguments were given to MyLibrary.foo/1:
|
||||
|
||||
#### Problem
|
||||
|
||||
Using multi-clause functions in Elixir, to group functions of the same name, is not an anti-pattern in itself. However, due to the great flexibility provided by this programming feature, some developers may abuse the number of guard clauses and pattern matches to group *unrelated* functionality.
|
||||
Using multi-clause functions in Elixir, to group functions of the same name, is a powerful Elixir feature. However, some developers may abuse this feature to group *unrelated* functionality, which configures an anti-pattern.
|
||||
|
||||
#### Example
|
||||
|
||||
A frequent example of this usage of multi-clause functions is when developers mix unrelated business logic into the same function definition. Such functions often have generic names or too broad specifications, making it difficult for maintainers and users of said functions to maintain and understand them.
|
||||
A frequent example of this usage of multi-clause functions is when developers mix unrelated business logic into the same function definition. Such functions often have generic names or too broad specifications, making it difficult for other developers to understand and maintain them.
|
||||
|
||||
Some developers may use documentation mechanisms such as `@doc` annotations to compensate for poor code readability, however the documentation itself may end-up full of conditionals to describe how the function behaves for each different argument combination.
|
||||
Some developers may use documentation mechanisms such as `@doc` annotations to compensate for poor code readability, however the documentation itself may end-up full of conditionals to describe how the function behaves for each different argument combination. This is a good indicator that the clauses are ultimately unrelated.
|
||||
|
||||
```elixir
|
||||
@doc """
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Meta-programming anti-patterns
|
||||
|
||||
This document outlines anti-patterns related to meta-programming.
|
||||
This document outlines potential anti-patterns related to meta-programming.
|
||||
|
||||
## Large code generation by macros
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Process-related anti-patterns
|
||||
|
||||
This document outlines anti-patterns related to processes and process-based abstractions.
|
||||
This document outlines potential anti-patterns related to processes and process-based abstractions.
|
||||
|
||||
## Code organization by process
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# What are anti-patterns?
|
||||
|
||||
Anti-patterns describe common mistakes or indicators of potential problems in code.
|
||||
Anti-patterns describe common mistakes or indicators of problems in code.
|
||||
They are also known as "code smells".
|
||||
|
||||
The goal of these guides is to document known anti-patterns found in Elixir software
|
||||
and teach developers how to identify and correct them. If an existing piece of code
|
||||
matches an anti-pattern, it does not mean your code must be rewritten. However, you
|
||||
should take its potential pitfalls and alternatives into consideration.
|
||||
The goal of these guides is to document potential anti-patterns found in Elixir software
|
||||
and teach developers how to identify them and their pitfalls. If an existing piece
|
||||
of code matches an anti-pattern, it does not mean your code must be rewritten.
|
||||
Sometimes, even if a snippet matches a potential anti-pattern and its limitations,
|
||||
it may be the best approach to the problem at hand. No codebase is free of anti-patterns
|
||||
and one should not aim to remove all of them.
|
||||
|
||||
The anti-patterns in these guides are broken into 4 main categories:
|
||||
|
||||
@@ -34,6 +36,8 @@ Each anti-pattern is documented using the following structure:
|
||||
* **Refactoring:** Ways to change your code to improve its qualities. Examples of refactored
|
||||
code are presented to illustrate these changes.
|
||||
|
||||
An additional section with "Additional Remarks" may be provided. Those may include known scenarios where the anti-pattern does not apply.
|
||||
|
||||
The initial catalog of anti-patterns was proposed by Lucas Vegi and Marco Tulio Valente, from [ASERG/DCC/UFMG](http://aserg.labsoft.dcc.ufmg.br/). For more info, see [Understanding Code Smells in Elixir Functional Language](https://github.com/lucasvegi/Elixir-Code-Smells/blob/main/etc/2023-emse-code-smells-elixir.pdf) and [the associated code repository](https://github.com/lucasvegi/Elixir-Code-Smells).
|
||||
|
||||
Additionally, the Security Working Group of the [Erlang Ecosystem Foundation](https://erlef.github.io/security-wg/) publishes [documents with security resources and best-practices of both Erland and Elixir, including detailed guides for web applications](https://erlef.github.io/security-wg/).
|
||||
Additionally, the Security Working Group of the [Erlang Ecosystem Foundation](https://erlef.github.io/security-wg/) publishes [documents with security resources and best-practices of both Erlang and Elixir, including detailed guides for web applications](https://erlef.github.io/security-wg/).
|
||||
|
||||
@@ -112,7 +112,7 @@ end
|
||||
|
||||
In the example above, the imported `List.duplicate/2` is only visible within that specific function. `duplicate/2` won't be available in any other function in that module (or any other module for that matter).
|
||||
|
||||
Note that `import`s are generally discouraged in the language. When working on your own code, prefer `alias` to `import`.
|
||||
While `import`s can be a useful for frameworks and libraries to build abstractions, developers should generally prefer `alias` to `import` on their own codebases, as aliases make the origin of the function being invoked clearer.
|
||||
|
||||
## use
|
||||
|
||||
|
||||
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.16 | Development
|
||||
1.15 | Bug fixes and security patches
|
||||
1.16 | Bug fixes and security patches
|
||||
1.15 | Security patches only
|
||||
1.14 | Security patches only
|
||||
1.13 | Security patches only
|
||||
1.12 | Security patches only
|
||||
1.11 | Security patches only
|
||||
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
|
||||
@@ -43,6 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
|
||||
|
||||
Elixir version | Supported Erlang/OTP versions
|
||||
:------------- | :-------------------------------
|
||||
1.16 | 24 - 26
|
||||
1.15 | 24 - 26
|
||||
1.14 | 23 - 25
|
||||
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
|
||||
|
||||
@@ -395,7 +395,7 @@ end
|
||||
|
||||
All of the constructs above are part of Elixir's syntax and have their own representation as part of the Elixir AST. This section will discuss the remaining constructs that are alternative representations of the constructs above. In other words, the constructs below can be represented in more than one way in your Elixir code and retain AST equivalence. We call this "Optional Syntax".
|
||||
|
||||
For a lightweight introduction to Elixir's Optional Syntax, [see this document](https://elixir-lang.org/getting-started/optional-syntax.html). Below we continue with a more complete reference.
|
||||
For a lightweight introduction to Elixir's Optional Syntax, [see this document](optional-syntax.md). Below we continue with a more complete reference.
|
||||
|
||||
### Integers in other bases and Unicode code points
|
||||
|
||||
|
||||
Reference in New Issue
Block a user