Understanding Anti-Patterns in JavaScript
Anti-Patterns: Understanding Anti-Patterns
What exactly is an anti-pattern in application development?
View Answer:
Can you define what an anti-pattern is in JavaScript?
View Answer:
Excessive variable definition in a global context pollutes the global namespace.
Passing strings rather than functions to setTimeout or setInterval causes the internal usage of eval().
Playing with native methods in the Object class prototype (this is a particularly bad anti-pattern).
Using JavaScript inline because it is inflexible.
The usage of document.write when native DOM alternatives, such as document.createElement, are preferable. Over the years, developers have misused document.write. Drawbacks include that it can overwrite the page we're on after the page loads, whereas document.createElement does not. It also doesn't work with XHTML, so using more DOM-friendly techniques like document.createElement is preferable.
Incorrect Use of True and False Evaluation
Naming Customs (Ninja Code)
Changing the DOM in a loop
New Array.prototype.reduce Object
What's the 'God Object' anti-pattern?
View Answer:
Here's an example of a 'God Object' anti-pattern in JavaScript. This App
object does too many things:
class App {
constructor(user) {
this.user = user;
this.db = new Database();
}
login() {
// login logic
}
logout() {
// logout logic
}
fetchUserFromDatabase() {
// database interaction logic
}
calculateUserStatistics() {
// complex calculations
}
renderUserProfile() {
// UI rendering logic
}
sendEmail() {
// Email sending logic
}
//... and so on for other methods
}
In this example, the App
object is responsible for authentication, data retrieval, user statistics calculation, UI rendering, email sending, etc., which violates the Single Responsibility Principle (SRP) and makes the App
object a 'God Object'.
How does the 'Spaghetti Code' anti-pattern manifest in JavaScript?
View Answer:
Here is a JavaScript example that exhibits the 'Spaghetti Code' anti-pattern:
var globalData = [];
function fetchData() {
// Fetches data and populates globalData
}
function process() {
fetchData();
for (var i = 0; i < globalData.length; i++) {
if (globalData[i] > 10) {
for (var j = 0; j < globalData.length; j++) {
if (globalData[j] < 20) {
// More nested conditionals, loops and so on...
}
}
}
}
}
process();
This code is a bad practice because it relies on a global variable, has no clear structure, and features complex nesting making it hard to read, understand, and maintain. The functions fetchData
and process
are tightly coupled, and changes to one might unexpectedly impact the other.
What's the 'Callback Hell' anti-pattern?
View Answer:
Sure, here's an example of the 'Callback Hell' anti-pattern in JavaScript:
function fetchData(callback) {
// Fetch data
callback(data);
}
function processData(data, callback) {
// Process data
callback(result);
}
function renderData(result, callback) {
// Render data
callback();
}
fetchData(function(data) {
processData(data, function(result) {
renderData(result, function() {
console.log('Done!');
});
});
});
In this code, callbacks are nested within callbacks, leading to a structure that's hard to read and maintain. This is often colloquially referred to as "pyramid of doom" due to the indentations forming a pyramid-like structure.
Can you explain the 'Global Variables' anti-pattern?
View Answer:
Here's an example of the 'Global Variables' anti-pattern in JavaScript:
var globalVar1 = 'Hello, world!';
var globalVar2 = [1, 2, 3, 4, 5];
var globalVar3 = {foo: 'bar'};
function doSomething() {
globalVar1 = 'Changed';
// Other manipulations using global variables
}
function doSomethingElse() {
globalVar2.push(6);
// Other manipulations using global variables
}
doSomething();
doSomethingElse();
console.log(globalVar1); // Output: 'Changed'
console.log(globalVar2); // Output: [1, 2, 3, 4, 5, 6]
In this example, globalVar1
, globalVar2
, and globalVar3
are global variables. They can be accessed and modified from anywhere in the code, making it difficult to manage their state and leading to potential bugs and confusion. The functions doSomething
and doSomethingElse
both manipulate the global variables, so it's hard to understand and predict the effects of these functions without knowing what they do internally.
What's the 'Copy and Paste Programming' anti-pattern?
View Answer:
Here's an example of the 'Copy and Paste Programming' anti-pattern in JavaScript:
function calculateAreaSquare(side) {
return side * side; // Logic for square area
}
function calculateAreaRectangle(length, breadth) {
return length * breadth; // Duplicate logic for rectangle area
}
console.log(calculateAreaSquare(5)); // Output: 25
console.log(calculateAreaRectangle(5, 5)); // Output: 25
In this example, the multiplication logic to calculate the area is duplicated in the functions calculateAreaSquare
and calculateAreaRectangle
. This is a simple case, but imagine if the logic was complex and repeated in multiple places, making the code harder to maintain and prone to errors. A more efficient approach would be to create a reusable function.
Code Solution:
Here is a better solution using a reusable function to avoid the 'Copy and Paste Programming' anti-pattern:
function calculateArea(length, breadth = length) {
return length * breadth;
}
console.log(calculateArea(5)); // Output: 25 (square)
console.log(calculateArea(5, 5)); // Output: 25 (rectangle)
In this improved version, the function calculateArea
is made more generic to calculate the area of both a square and a rectangle. By setting breadth = length
as a default parameter, the function can handle both cases, eliminating the need for duplicated logic. This makes the code easier to maintain, reduces the risk of errors, and promotes reusability.
Can you tell me about the 'Magic Numbers' anti-pattern?
View Answer:
Here's an example of the 'Magic Numbers' anti-pattern and its solution in JavaScript:
Magic Numbers Anti-pattern:
function calculateArea(radius) {
return 3.14159 * radius * radius; // What does 3.14159 represent?
}
console.log(calculateArea(5)); // Output: 78.53975
Solution:
const PI = 3.14159; // Define a named constant for the magic number
function calculateArea(radius) {
return PI * radius * radius; // Much clearer!
}
console.log(calculateArea(5)); // Output: 78.53975
In the first example, it's not immediately clear what 3.14159
represents. This is an example of a 'magic number'. The improved code replaces this magic number with a named constant PI
, making it much clearer what this number represents and why it's being used. The named constant is also easier to maintain and change if necessary.
How does the 'Monkey Patching' anti-pattern affect code quality?
View Answer:
Monkey Patching Anti-pattern:
// Modifying the built-in Array prototype
Array.prototype.first = function() {
return this[0];
}
var myArray = [1, 2, 3];
console.log(myArray.first()); // Output: 1
Solution:
// Define a function that accepts an array instead
function getFirst(array) {
return array[0];
}
var myArray = [1, 2, 3];
console.log(getFirst(myArray)); // Output: 1
In the first example, the built-in Array
prototype is modified to add a first
method. This can cause unexpected behavior if other parts of your code or libraries also attempt to modify Array
, and can make debugging difficult.
The improved code defines a separate getFirst
function that accepts an array as a parameter. This avoids modifying the Array
prototype and any potential conflicts or issues that could arise from doing so.