레퍼런스

[Medium] 클린 코드: Null 반환을 피하는 법

둘기덕 2024. 3. 11. 22:41
반응형

NullPointerException 위험을 줄이려면 메서드에서 null 값을 반환하지 않도록 노력하세요. 값이 없는 경우를 전달하기 위해서는 Optional, 빈 컬렉션, 또는 에러 핸들링을 사용하는 것과 같은 대체 전략을 사용하십시오.

 

 

📌 null을 반환하는 대신, Optional을 사용하여 값의 부재를 명시적으로 표현하는 것을 고려하세요. 이를 통해 클라이언트는 값이 있는 경우와 없는 경우를 모두 처리할 수 있습니다.

import java.util.Optional;

// A service class that may or may not find a user
class UserService {
    // Simulating a method that may or may not find a user by ID
    public Optional<User> findUserById(int userId) {
        // In a real-world scenario, this method might interact with a database or another data source
        // Here, we'll just return an Optional based on a simple condition
        if (userId > 0 && userId <= 100) {
            return Optional.of(new User(userId, "John Doe"));
        } else {
            return Optional.empty();
        }
    }
}

// User class representing a user
class User {
    private final int id;
    private final String name;
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}

public class OptionalExample {
    public static void main(String[] args) {
        UserService userService = new UserService();
        // Attempting to find a user with a valid ID
        Optional<User> userOptional = userService.findUserById(42);

        // Handling the presence or absence of the user
        if (userOptional.isPresent()) {
            User user = userOptional.get();
            System.out.println("User found: " + user.getName());
        } else {
            System.out.println("User not found.");
        }

        // Using the orElse method to provide a default value if the user is not found
        User defaultUser = userService.findUserById(101).orElse(new User(0, "Default User"));
        System.out.println("Default User: " + defaultUser.getName());
    }
}

 

 

📌 컬렉션을 처리할 때, null 대신 빈 컬렉션을 반환하세요. 이렇게 하면 클라이언트 코드가 간소화되어 null 검사가 필요하지 않고 일관된 인터페이스를 제공할 수 있습니다.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

// A service class that may or may not return a list of items
class ItemService {
    // Simulating a method that may or may not find items
    public List<String> findItems() {
        // In a real-world scenario, this method might interact with a database or another data source
        // Here, we'll just return an empty list based on a simple condition
        if (Math.random() > 0.5) {
            List<String> items = new ArrayList<>();
            items.add("Item 1");
            items.add("Item 2");
            items.add("Item 3");
            return items;
        } else {
            return Collections.emptyList(); // Returning an empty list instead of null
        }
    }
}

public class EmptyCollectionExample {
    public static void main(String[] args) {
        ItemService itemService = new ItemService();
        // Attempting to find items
        List<String> foundItems = itemService.findItems();

        // No need for null checks - directly iterate over the collection
        for (String item : foundItems) {
            System.out.println("Found item: " + item);
        }

        // Handling an empty list without the need for explicit null checks
        if (foundItems.isEmpty()) {
            System.out.println("No items found.");
        } else {
            System.out.println("Total items found: " + foundItems.size());
        }
    }
}

 

 

📌 값이 없는 상태를 나타내는 특정한 객체를 생성하여 Null Object Pattern을 구현하세요. 이 객체는 실제 객체와 동일한 인터페이스 또는 클래스를 준수해야 합니다.

// Interface representing a shape
interface Shape {
    void draw();
}

// Concrete implementation of Shape for Circle
class Circle implements Shape {
    private final int radius;
    public Circle(int radius) {
        this.radius = radius;
    }
    @Override
    public void draw() {
        System.out.println("Drawing Circle with radius " + radius);
    }
}

// NullObject representing the absence of a Shape
class NullShape implements Shape {
    @Override
    public void draw() {
        // Do nothing or provide a default behavior for the absence of a shape
        System.out.println("No shape to draw.");
    }
}

// Client code using the Shape interface
public class NullObjectPatternExample {
    public static void main(String[] args) {
        // Drawing a Circle
        Shape circle = new Circle(5);
        circle.draw();

        // Attempting to draw a null shape (traditional approach)
        Shape nullShape = null;
        if (nullShape != null) {
            nullShape.draw();
        } else {
            System.out.println("Null shape encountered.");
        }

        // Using Null Object Pattern to represent the absence of a shape
        Shape nullObjectShape = new NullShape();
        nullObjectShape.draw();
    }
}

 

 

 

📌 값의 부재가 예외적인 조건을 나타내는 경우 null을 반환하는 것보다 예외를 던지는 것을 고려하십시오.

import java.util.HashMap;
import java.util.Map;

// Custom exception for representing the absence of a value
class NotFoundException extends RuntimeException {
    public NotFoundException(String message) {
        super(message);
    }
}

// Service class that may throw NotFoundException
class ItemService {
    private Map<String, String> itemMap;
    public ItemService() {
        this.itemMap = new HashMap<>();
        itemMap.put("item1", "Item 1");
        itemMap.put("item2", "Item 2");
        itemMap.put("item3", "Item 3");
    }
    // Method that throws NotFoundException if the item is not found
    public String getItemByName(String itemName) {
        String item = itemMap.get(itemName);
        if (item == null) {
            throw new NotFoundException("Item not found: " + itemName);
        }
        return item;
    }
}

public class ExceptionInsteadOfNullExample {
    public static void main(String[] args) {
        ItemService itemService = new ItemService();
        try {
            // Trying to get an item by name
            String item = itemService.getItemByName("item2");
            System.out.println("Item found: " + item);
            // Trying to get a non-existent item (throws NotFoundException)
            String nonExistentItem = itemService.getItemByName("item4");
            System.out.println("This line won't be reached."); // This won't be printed
        } catch (NotFoundException e) {
            System.out.println("Exception caught: " + e.getMessage());
        }
    }
}

 

 

📌 값이 없음을 나타내기 위해 특정 반환 값을 지정합니다. 예를 들어, 오류 조건을 나타내기 위해 특수 문자열이나 상수를 반환합니다.

// Service class that returns a special string to indicate errors or absence of a value
class DataService {
    private static final String ERROR_STRING = "Error: Unable to retrieve data";

  // Simulating a method that may return data or an error string
    public String getDataById(int id) {
        // In a real-world scenario, this method might interact with a database or another data source
        // Here, we'll simulate a condition where data is not found
        if (id >= 1 && id <= 100) {
            return "Data for ID " + id;
        } else {
            return ERROR_STRING; // Special string to indicate an error or absence of data
        }
    }
}

public class ReturnValueForErrorExample {
    public static void main(String[] args) {
        DataService dataService = new DataService();
        // Trying to get data by ID
        String data1 = dataService.getDataById(42);
        if (data1.equals(dataService.ERROR_STRING)) {
            System.out.println("Error: Data not found.");
        } else {
            System.out.println("Data found: " + data1);
        }

        // Trying to get data for a non-existent ID
        String data2 = dataService.getDataById(150);
        if (data2.equals(dataService.ERROR_STRING)) {
            System.out.println("Error: Data not found.");
        } else {
            System.out.println("Data found: " + data2);
        }
    }
}

 

 

📌 null을 반환하는 대신 기본값을 제공합니다. 이를 통해 클라이언트는 항상 의미 있는 결과를 얻을 수 있습니다.

// Service class that provides default values
class ConfigurationService {
    private static final String DEFAULT_COLOR = "White";
    private static final int DEFAULT_FONT_SIZE = 12;

    // Simulating a method that may return a configuration value or a default value
    public String getColor(String elementId) {
        // In a real-world scenario, this method might read configuration values
        // Here, we'll simulate a condition where the color is not configured
        // Instead of returning null, we provide a default color
        if ("header".equals(elementId)) {
            return "Blue"; // Configured color for the header
        } else {
            return DEFAULT_COLOR; // Default color when the configuration is not available
        }
    }

    // Simulating a method that may return an integer configuration value or a default value
    public int getFontSize(String elementId) {
        // In a real-world scenario, this method might read configuration values
        // Here, we'll simulate a condition where the font size is not configured
        // Instead of returning null, we provide a default font size
        if ("body".equals(elementId)) {
            return 16; // Configured font size for the body
        } else {
            return DEFAULT_FONT_SIZE; // Default font size when the configuration is not available
        }
    }
}

public class DefaultValueInsteadOfNullExample {
    public static void main(String[] args) {
        ConfigurationService configurationService = new ConfigurationService();
        // Getting color for the header
        String headerColor = configurationService.getColor("header");
        System.out.println("Header Color: " + headerColor);

        // Getting color for a non-existent element (returns default color)
        String unknownElementColor = configurationService.getColor("unknownElement");
        System.out.println("Unknown Element Color: " + unknownElementColor);

        // Getting font size for the body
        int bodyFontSize = configurationService.getFontSize("body");
        System.out.println("Body Font Size: " + bodyFontSize);

        // Getting font size for a non-existent element (returns default font size)
        int unknownElementFontSize = configurationService.getFontSize("unknownElement");
        System.out.println("Unknown Element Font Size: " + unknownElementFontSize);
    }
}

 

 

📌 메서드가 특정 상황에서 null을 반환할 수 있는 경우 문서에 이 동작을 명확하게 기록합니다. 이는 개발자가 잠재적인 null 값을 이해하고 처리하는 데 도움이 됩니다.

 

 

📌 null을 피할 수 없는 경우 Objects.requireNull 또는 조건부 검사를 사용해서 안전한 null 처리 방법을 사용하여 NullPointerException 위험을 최소화합니다.

import java.util.Objects;

// Service class that may return null in certain cases
class UserService {
    // Simulating a method that may return a user object or null
    public User findUserById(int userId) {
        // In a real-world scenario, this method might interact with a database or another data source
        // Here, we'll simulate a condition where the user is not found
        if (userId > 0 && userId <= 100) {
            return new User(userId, "John Doe");
        } else {
            return null;
        }
    }
}

// User class representing a user
class User {
    private final int id;
    private final String name;
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
}

public class SafeNullHandlingExample {
    public static void main(String[] args) {
        UserService userService = new UserService();
        // Attempting to find a user by ID
        User user = userService.findUserById(42);
        // Encouraging safe null handling practices
        if (user != null) {
            // Performing actions with the user object
            System.out.println("User found: " + user.getName());
            // Using Objects.requireNonNull for additional safety
            processUser(Objects.requireNonNull(user));
        } else {
            System.out.println("User not found.");
        }
    }

    // Example method that processes a user object (additional functionality)
    private static void processUser(User user) {
        System.out.println("Processing user with ID " + user.getId());
    }
}

 

 

📌 만약 null이 예기치 않은 프로그래밍 오류를 나타내는 경우, NullPointerException을 허용하여 "Fail Fast" 원칙을 받아들입니다. 이는 개발 중 문제를 식별하고 해결하는 데 도움이 됩니다. 

// Service class that throws NullPointerException for unexpected null values
class AccountService {
    // Simulating a method that expects a non-null account object
    public void processAccount(Account account) {
        // Using Objects.requireNonNull to fail fast if the account is null
        Objects.requireNonNull(account, "Account cannot be null");

        // Processing the account (example functionality)
        System.out.println("Processing account with ID " + account.getId());
    }
}

// Account class representing an account
class Account {
    private final int id;
    public Account(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
}

public class FailFastExample {
    public static void main(String[] args) {
        AccountService accountService = new AccountService();

        // Attempting to process an account (non-null case)
        Account validAccount = new Account(123);
        accountService.processAccount(validAccount);

        // Attempting to process a null account (programming error)
        Account invalidAccount = null;
        try {
            accountService.processAccount(invalidAccount);
        } catch (NullPointerException e) {
            // Handling the NullPointerException (printing or logging the error)
            System.err.println("NullPointerException caught: " + e.getMessage());
        }
    }
}

 

 

📌 최신 프로그래밍 언어를 사용하는 경우, Kotlin의 nullable type 이나 Swift의 optionals와 같이 더 안전한 널 핸들링 기능을 탐색합니다.

 

null을 반환하는 것에 대한 대안을 채택함으로써 코드의 명확성, 안전성 및 유지보수성을 향상합니다. null을 피하는 것은 optional, 에러 핸들링 또는 기타 패턴을 사용하는 것을 통해 NullPointerException을 방지합니다.

 

출처: https://medium.com/@bubu.tripathy/clean-code-avoid-returning-null-9353498d6a01

반응형