You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: lib/elixir/pages/anti-patterns/macro-anti-patterns.md
+97-2
Original file line number
Diff line number
Diff line change
@@ -4,11 +4,106 @@ This document outlines anti-patterns related to meta-programming.
4
4
5
5
## Unnecessary macros
6
6
7
-
TODO.
7
+
#### Problem
8
+
9
+
**Macros** are powerful meta-programming mechanisms that can be used in Elixir to extend the language. While using macros is not an anti-pattern in itself, this meta-programming mechanism should only be used when absolutely necessary. Whenever a macro is used, but it would have been possible to solve the same problem using functions or other existing Elixir structures, the code becomes unnecessarily more complex and less readable. Because macros are more difficult to implement and reason about, their indiscriminate use can compromise the evolution of a system, reducing its maintainability.
10
+
11
+
#### Example
12
+
13
+
The `MyMath` module implements the `sum/2` macro to perform the sum of two numbers received as parameters. While this code has no syntax errors and can be executed correctly to get the desired result, it is unnecessarily more complex. By implementing this functionality as a macro rather than a conventional function, the code became less clear:
14
+
15
+
```elixir
16
+
defmoduleMyMathdo
17
+
defmacrosum(v1, v2) do
18
+
quotedo
19
+
unquote(v1) +unquote(v2)
20
+
end
21
+
end
22
+
end
23
+
```
24
+
```elixir
25
+
iex>requireMyMath
26
+
MyMath
27
+
iex>MyMath.sum(3, 5)
28
+
8
29
+
iex>MyMath.sum(3+1, 5+6)
30
+
15
31
+
```
32
+
33
+
#### Refactoring
34
+
35
+
To remove this anti-pattern, the developer must replace the unnecessary macro with structures that are simpler to write and understand, such as named functions. The code shown below is the result of the refactoring of the previous example. Basically, the `sum/2` macro has been transformed into a conventional named function. Note that the `require/2` call is no longer needed:
36
+
37
+
```elixir
38
+
defmoduleMyMathdo
39
+
defsum(v1, v2) do# <= The macro became a named function
40
+
v1 + v2
41
+
end
42
+
end
43
+
```
44
+
```elixir
45
+
iex>MyMath.sum(3, 5)
46
+
8
47
+
iex>MyMath.sum(3+1, 5+6)
48
+
15
49
+
```
8
50
9
51
## Large code generation by macros
10
52
11
-
TODO.
53
+
#### Problem
54
+
55
+
This anti-pattern is related to macros that generate too much code. When a macro generates a large amount of code, it impacts how the compiler and/or the runtime work. The reason for this is that Elixir may have to expand, compile, and execute the code multiple times, which will make compilation slower and the resulting compiled artifacts larger.
56
+
57
+
#### Example
58
+
59
+
Imagine you are defining a router for a web application, where you could have macros like `get/2`. On every invocation of the macro (which could be hundreds), the code inside `get/2` will be expanded and compiled, which can generate a large volume of code overall.
60
+
61
+
```elixir
62
+
defmoduleRoutesdo
63
+
defmacroget(route, handler) do
64
+
quotedo
65
+
route =unquote(route)
66
+
handler =unquote(handler)
67
+
68
+
ifnotis_binary(route) do
69
+
raiseArgumentError, "route must be a binary"
70
+
end
71
+
72
+
ifnotis_atom(handler) do
73
+
raiseArgumentError, "route must be a module"
74
+
end
75
+
76
+
@store_route_for_compilation {route, handler}
77
+
end
78
+
end
79
+
end
80
+
```
81
+
82
+
#### Refactoring
83
+
84
+
To remove this anti-pattern, the developer should simplify the macro, delegating part of its work to other functions. As shown below, by encapsulating the code inside `quote/1` inside the function `__define__/3` instead, we reduce the code that is expanded and compiled on every invocation of the macro, and instead we dispatch to a function to do the bulk of the work.
0 commit comments