0/5
## Understanding and Refactoring the "Magic Numbers" Antipattern In software development, and particularly in smart contract programming where clarity and security are paramount, we often encounter "antipatterns." An antipattern is essentially a common practice that appears beneficial or straightforward initially but ultimately leads to suboptimal, fragile, or hard-to-maintain code. Think of it as a coding habit that should generally be avoided. One prevalent antipattern is the use of "Magic Numbers." Let's explore what they are, why they're problematic, and how to refactor them using a practical Vyper example. ## Identifying Magic Numbers in Code Consider a function designed to calculate the USD value of a given ETH amount, perhaps using an external price feed. Inside this function, you might find a calculation like this: ```vyper # Inside a function like get_eth_to_usd_rate # Assumes eth_price (USD per ETH, possibly with extra decimals) and eth_amount are provided eth_amount_in_usd: uint256 = (eth_price * eth_amount) // (1 * (10 ** 18)) ``` Focus on the divisor: `(1 * (10 ** 18))`. This number, `10**18` (one quintillion), appearing directly within the calculation is a classic example of a magic number. It's "magic" because its meaning and purpose aren't immediately clear just by looking at the code. ## Why Magic Numbers Are Problematic Using unexplained numeric literals directly in your code introduces several issues: 1. **Poor Readability:** When another developer (or even yourself, weeks later) reads this line, they have to pause and decipher *what* `10**18` represents. Why is this specific number being used for division? Its purpose isn't self-documenting. Is it related to decimal precision, a specific scaling factor, or something else entirely? 2. **Difficult Maintenance:** Imagine this same value, representing the same underlying concept (like decimal precision), is used in multiple places throughout your smart contract. If you ever need to change this value (perhaps due to a change in an external dependency or a system-wide parameter), you must find and manually update every single instance. Missing even one occurrence can introduce subtle and potentially costly bugs. 3. **Obscured Logic:** In more complex mathematical operations, embedding literal numbers can make the overall logic much harder to follow and verify. The origin and significance of the numbers get lost in the calculation. A fundamental rule of thumb in programming is to avoid magic numbers. Numeric literals shouldn't just "float" within your functions without a clear explanation of their meaning. ## The Solution: Using Named Constants The recommended approach to eliminate magic numbers is to replace them with **named constants**. A constant is a variable whose value is fixed (typically at compile time in Vyper) and cannot be changed during contract execution. By assigning the magic number to a constant with a descriptive name, you make the code self-explanatory. Here's how you would implement this in Vyper: 1. **Declare a Constant:** Define a constant at the top level of your contract, often grouped with other constants or immutable variables. ```vyper # Contract level declarations (e.g., under '# Constants & Immutables') # Represents the standard 18 decimal places for ETH/Wei precision PRECISION: constant(uint256) = 1 * (10 ** 18) ``` * `PRECISION`: This name clearly indicates the purpose of the number – it relates to the decimal precision used in calculations, likely for handling Ether values which have 18 decimals. * `constant(uint256)`: This declares `PRECISION` as a constant of type `uint256`. Its value is set at compile time. * `1 * (10 ** 18)`: The value previously identified as the magic number is assigned to this constant. Note: Constants like this typically don't need `public` visibility unless intended for external reference. The parentheses around `1 * (10 ** 18)` aren't strictly necessary due to mathematical order of operations but can be used for explicit clarity. 2. **Refactor the Code:** Replace the magic number in your function with the newly defined constant. ```vyper # Inside the get_eth_to_usd_rate function (refactored) eth_amount_in_usd: uint256 = (eth_price * eth_amount) // PRECISION ``` ## Benefits of Refactoring The refactored code is significantly improved: * **Enhanced Readability:** It's now immediately clear that the division involves a factor related to `PRECISION`. The *intent* of the operation is evident without needing external comments or guesswork. * **Improved Maintainability:** If the underlying value for precision ever needed changing (though unlikely for standard Ether decimals), you would only need to update it in one place: the constant declaration. This change would automatically propagate to all locations where `PRECISION` is used, reducing the risk of errors. ## Context: `10**18` in Ethereum The number `1 * (10**18)` frequently appears in Ethereum smart contracts because Ether itself is divisible down to 18 decimal places. The smallest unit is Wei, and 1 Ether equals 10<sup>18</sup> Wei. Calculations involving token amounts or price feeds often require adjustments to account for these decimals, making a constant named `PRECISION` or `DECIMALS` highly relevant and useful. You might encounter other numbers within functions (like a `10**10` used for scaling a price feed, as might exist elsewhere in the `get_eth_to_usd_rate` function) that could also be candidates for refactoring into named constants if their purpose isn't immediately obvious from the context. By replacing magic numbers with well-named constants, you create smart contracts that are significantly easier to read, understand, debug, and maintain – crucial attributes for building robust and reliable web3 applications.
A practical tutorial for Understanding and Refactoring the "Magic Numbers" Antipattern - Identify the 'magic number' antipattern in smart contracts and learn the best practice of using named constants in Vyper to enhance code clarity and ease maintenance.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025
Duration: 2h 08min
Duration: 2h 32min
Duration: 24min
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025