8 Möglichkeiten zur Optimierung von doppeltem und redundantem Code!

In der täglichen Entwicklung stoßen wir häufig auf sich wiederholenden und redundanten Code. Jeder weiß, dass das Duplizieren von Code nicht gut ist und hauptsächlich folgende Mängel aufweist: schlechte Wartbarkeit, schlechte Lesbarkeit, erhöhtes Fehlerrisiko usw. Kürzlich habe ich einige doppelte Codes im System optimiert und mehrere Methoden verwendet, die ich sehr nützlich fand. In diesem Artikel erfahren Sie, wie Sie sich wiederholenden und redundanten Code optimieren können

Extrahieren Sie öffentliche Methoden,
extrahieren Sie eine Dienstprogrammklasse,
Reflexion ,
Generika
, Vererbung und Polymorphismus,
Entwurfsmuster und
funktionale Lambda
AOP-Aspekte

1. Öffentliche Methoden extrahieren

Das Extrahieren öffentlicher Methoden ist die am häufigsten verwendete Methode zur Code-Deduplizierung

In diesem Beispiel durchlaufen Sie beispielsweise die Namensliste separat, konvertieren sie dann in Groß- und Kleinbuchstaben und drucken sie aus:

public class TianLuoExample {
    
    

    public static void main(String[] args) {
    
    
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "TianLuo");

        System.out.println("Uppercase Names:");
        for (String name : names) {
    
    
            String uppercaseName = name.toUpperCase();
            System.out.println(uppercaseName);
        }

        System.out.println("Lowercase Names:");
        for (String name : names) {
    
    
            String lowercaseName = name.toLowerCase();
            System.out.println(lowercaseName);
        }
    }
}

Offensichtlich ist der Prozess des Durchlaufens von Namen repetitiv und redundant, aber der Konvertierungsfall ist anders. Wir können eine öffentliche Methode „processNames“ nehmen und sie wie folgt optimieren:

public class TianLuoExample {
    
    

    public static void processNames(List<String> names, Function<String, String> nameProcessor, String processType) {
    
    
        System.out.println(processType + " Names:");
        for (String name : names) {
    
    
            String processedName = nameProcessor.apply(name);
            System.out.println(processedName);
        }
    }

    public static void main(String[] args) {
    
    
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "TianLuo");

        processNames(names, String::toUpperCase, "Uppercase");
        processNames(names, String::toLowerCase, "Lowercase");
    }
}

2. Extrahieren von Werkzeugen

Wir optimieren doppelten Code und extrahieren eine öffentliche Methode. Wenn wir feststellen, dass diese Methode mehr Gemeinsamkeiten aufweist, können wir die öffentliche Methode zu einer Toolklasse aktualisieren. In diesem Geschäftsszenario müssen beispielsweise beim Registrieren, Ändern der E-Mail-Adresse, Zurücksetzen des Kennworts usw. alle E-Mail-Adressen überprüft werden

Bei der Implementierung der Registrierungsfunktion gibt der Benutzer die E-Mail-Adresse ein und muss das E-Mail-Format überprüfen.

public class RegisterServiceImpl implements RegisterService{
    
    
    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    public boolean registerUser(UserInfoReq userInfo) {
    
    
        String email = userInfo.getEmail();
        Pattern pattern = Pattern.compile(EMAIL_REGEX);
        Matcher emailMatcher = pattern.matcher(email);
        if (!emailMatcher.matches()) {
    
    
            System.out.println("Invalid email address.");
            return false;
        }

        // 进行其他用户注册逻辑,比如保存用户信息到数据库等
        // 返回注册结果
        return true;
    }
}

Im Rahmen des Vorgangs zum Zurücksetzen des Passworts wird dem Benutzer normalerweise ein Link oder ein Bestätigungscode bereitgestellt, der an die E-Mail-Adresse des Benutzers gesendet werden muss. In diesem Fall müssen Sie auch die Rechtmäßigkeit des E-Mail-Formats überprüfen:

public class PasswordServiceImpl implements PasswordService{
    
    

    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    public void resetPassword(PasswordInfo passwordInfo) {
    
    
        Pattern pattern = Pattern.compile(EMAIL_REGEX);
        Matcher emailMatcher = pattern.matcher(passwordInfo.getEmail());
        if (!emailMatcher.matches()) {
    
    
            System.out.println("Invalid email address.");
            return false;
        }
        //发送通知修改密码
        sendReSetPasswordNotify();
    }
}

Wir können eine Methode zum Überprüfen des Postfachs extrahieren, und da die Funktion zum Überprüfen des Postfachs in einer anderen Klasse liegt, können wir eine Toolklasse zum Überprüfen des Postfachs extrahieren:

public class EmailValidatorUtil {
    
    
    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    private static final Pattern pattern = Pattern.compile(EMAIL_REGEX);

    public static boolean isValid(String email) {
    
    
        Matcher matcher = pattern.matcher(email);
        return matcher.matches();
    }
}

//注册的代码可以简化为这样啦
public class RegisterServiceImpl implements RegisterService{
    
    
    
    public boolean registerUser(UserInfoReq userInfo) {
    
    
        if (!EmailValidatorUtil.isValid(userInfo.getEmail())) {
    
    
            System.out.println("Invalid email address.");
            return false;
        }

        // 进行其他用户注册逻辑,比如保存用户信息到数据库等
        // 返回注册结果
        return true;
    }
}

3. Reflexion

In unserer täglichen Entwicklung müssen wir häufig PO, DTO und VO konvertieren. Daher sieht man oft ähnlichen Code:

 //DTO 转VO
    public UserInfoVO convert(UserInfoDTO userInfoDTO) {
    
    
        UserInfoVO userInfoVO = new UserInfoVO();
        userInfoVO.setUserName(userInfoDTO.getUserName());
        userInfoVO.setAge(userInfoDTO.getAge());
        return userInfoVO;
    }
      //PO 转DTO
    public UserInfoDTO convert(UserInfoPO userInfoPO) {
    
    
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        userInfoDTO.setUserName(userInfoPO.getUserName());
        userInfoDTO.setAge(userInfoPO.getAge());
        return userInfoDTO;
    }

Wir können BeanUtils.copyProperties() verwenden, um doppelten Code zu entfernen. Die unterste Ebene von BeanUtils.copyProperties() verwendet Reflektion:

public UserInfoVO convert(UserInfoDTO userInfoDTO) {
UserInfoVO userInfoVO = new UserInfoVO();
BeanUtils.copyProperties(userInfoDTO, userInfoVO);
return userInfoVO;
}

public UserInfoDTO convert(UserInfoPO userInfoPO) {
UserInfoDTO userInfoDTO = new UserInfoDTO();
BeanUtils.copyProperties(userInfoPO,userInfoDTO);
return userInfoDTO;
}

4. Generika

Wie beseitigen Generika doppelten Code? Lassen Sie mich Ihnen ein Beispiel geben. Ich habe einen geschäftlichen Bedarf, Überweisungsdetails und Überweisungssalden zu vergleichen. Es gibt zwei ähnliche Methoden:

private void getAndUpdateBalanceResultMap(String key, Map<String, List<TransferBalanceDTO>> compareResultListMap,
List<TransferBalanceDTO> balanceDTOs) {
    
    
    List<TransferBalanceDTO> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
    tempList.addAll(balanceDTOs);
    compareResultListMap.put(key, tempList);
}

private void getAndUpdateDetailResultMap(String key, Map<String, List<TransferDetailDTO>> compareResultListMap,
                                          List<TransferDetailDTO> detailDTOS) {
    
    
    List<TransferDetailDTO> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
    tempList.addAll(detailDTOS);
    compareResultListMap.put(key, tempList);
}

Die Prozessfunktionen dieser beiden Codeteile sehen sehr ähnlich aus, können jedoch nicht direkt zusammengeführt werden, um eine gemeinsame Methode zu extrahieren, da die Typen inkonsistent sind. Wenn die einfachen Typen unterschiedlich sind, können wir sie mit der generischen Verarbeitung kombinieren, da das Wesen von Generika in parametrisierten Typen besteht. Die Optimierung ist wie folgt:

private <T> void getAndUpdateResultMap(String key, Map<String, List<T>> compareResultListMap, List<T> accountingDTOS) {
    
    
List<T> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
tempList.addAll(accountingDTOS);
compareResultListMap.put(key, tempList);
}

5. Vererbung und Polymorphismus

Angenommen, Sie entwickeln eine E-Commerce-Plattform und müssen verschiedene Arten von Bestellungen abwickeln, beispielsweise reguläre Bestellungen und Rabattbestellungen. Jede Art von Bestellung verfügt über einige gemeinsame Attribute (z. B. Bestellnummer, Einkaufsartikelliste) und Methoden (z. B. Berechnung des Gesamtpreises, Generierung eines Bestellberichts), aber Rabattbestellungen verfügen auch über spezifische Attribute und Methoden.

Ohne Vererbung und Polymorphismus würden Sie Code wie diesen schreiben:

//普通订单
public class Order {
    
    
    private String orderNumber;
    private List<Product> products;

    public Order(String orderNumber, List<Product> products) {
    
    
        this.orderNumber = orderNumber;
        this.products = products;
    }

    public double calculateTotalPrice() {
    
    
        double total = 0;
        for (Product product : products) {
    
    
            total += product.getPrice();
        }
        return total;
    }
    
      public String generateOrderReport() {
    
    
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

//折扣订单
public class DiscountOrder {
    
    
    private String orderNumber;
    private List<Product> products;
    private double discountPercentage;

    public DiscountOrder(String orderNumber, List<Product> products, double discountPercentage) {
    
    
        this.orderNumber = orderNumber;
        this.products = products;
        this.discountPercentage = discountPercentage;
    }

    public double calculateTotalPrice() {
    
    
        double total = 0;
        for (Product product : products) {
    
    
            total += product.getPrice();
        }
        return total - (total * discountPercentage / 100);
    }
      public String generateOrderReport() {
    
    
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

Beachten Sie natürlich, dass der Code der Methode „generateOrderReport()“ in den Klassen „Order“ und „DiscountOrder“ genau derselbe ist. berechnenTotalPrice() ist etwas anders, aber ganz anders.

Wir können Vererbung und Polymorphismus verwenden, um doppelten Code zu entfernen und DiscountOrder Order erben zu lassen. Der Code lautet wie folgt:

public class Order {
    
    
    private String orderNumber;
    private List<Product> products;

    public Order(String orderNumber, List<Product> products) {
    
    
        this.orderNumber = orderNumber;
        this.products = products;
    }

    public double calculateTotalPrice() {
    
    
        double total = 0;
        for (Product product : products) {
    
    
            total += product.getPrice();
        }
        return total;
    }

    public String generateOrderReport() {
    
    
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

public class DiscountOrder extends Order {
    
    
    private double discountPercentage;

    public DiscountOrder(String orderNumber, List<Product> products, double discountPercentage) {
    
    
        super(orderNumber, products);
        this.discountPercentage = discountPercentage;
    }

    @Override
    public double calculateTotalPrice() {
    
    
        double total = super.calculateTotalPrice();
        return total - (total * discountPercentage / 100);
    }
}

6. Verwenden Sie Designmuster

Viele Entwurfsmuster können sich wiederholenden Code reduzieren, die Lesbarkeit und Skalierbarkeit des Codes verbessern. Zum Beispiel:

Factory-Muster: Durch das Factory-Muster können Sie die Erstellung und Verwendung von Objekten trennen und so die wiederholte Codeerstellung reduzieren.
Strategiemuster: Das Strategiemuster definiert eine Familie von Algorithmen, kapselt sie in unabhängige Klassen und macht sie austauschbar. Durch die Verwendung des Strategiemusters können Sie die Notwendigkeit reduzieren, dieselbe Logik in Ihrem Code wiederzuverwenden.
Vorlagenmethodenmuster: Das Vorlagenmethodenmuster definiert das Grundgerüst eines Algorithmus und verschiebt einige Schritte auf Unterklassen. Dies hilft, die Duplizierung ähnlichen Codes in verschiedenen Klassen zu vermeiden.
Lassen Sie mich Ihnen ein Beispiel geben, wie die Vorlagenmethode doppelten Code entfernt. Geschäftsszenario:

Angenommen, Sie entwickeln ein Verfahren zur Kaffee- und Teezubereitung. Die Schritte des Heißwassers und der Zugabe von Substanzen sind die gleichen, aber die spezifischen Schritte der Getränkezubereitung sind unterschiedlich.

Ohne die Verwendung des Template-Methodenmusters wäre die Implementierung chaotisch:

public class Coffee {
    
    
    public void prepareCoffee() {
    
    
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addCondiments();
    }

    private void boilWater() {
    
    
        System.out.println("Boiling water");
    }

    private void brewCoffeeGrinds() {
    
    
        System.out.println("Brewing coffee grinds");
    }

    private void pourInCup() {
    
    
        System.out.println("Pouring into cup");
    }

    private void addCondiments() {
    
    
        System.out.println("Adding sugar and milk");
    }
}

public class Tea {
    
    
    public void prepareTea() {
    
    
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    private void boilWater() {
    
    
        System.out.println("Boiling water");
    }

    private void steepTeaBag() {
    
    
        System.out.println("Steeping the tea bag");
    }

    private void pourInCup() {
    
    
        System.out.println("Pouring into cup");
    }

    private void addLemon() {
    
    
        System.out.println("Adding lemon");
    }
}

In diesem Codebeispiel können wir feststellen, dass die Schrittcodes zum Kochen von Wasser und zum Eingießen in Tassen in den Klassen Kaffee und Tee wiederholt werden.

Mithilfe des Template-Methodenmusters kann der Code wie folgt optimiert werden:

abstract class Beverage {
    
    
    public final void prepareBeverage() {
    
    
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    private void boilWater() {
    
    
        System.out.println("Boiling water");
    }

    abstract void brew();

    private void pourInCup() {
    
    
        System.out.println("Pouring into cup");
    }

    abstract void addCondiments();
}

class Coffee extends Beverage {
    
    
    @Override
    void brew() {
    
    
        System.out.println("Brewing coffee grinds");
    }

    @Override
    void addCondiments() {
    
    
        System.out.println("Adding sugar and milk");
    }
}

class Tea extends Beverage {
    
    
    @Override
    void brew() {
    
    
        System.out.println("Steeping the tea bag");
    }

    @Override
    void addCondiments() {
    
    
        System.out.println("Adding lemon");
    }
}

In diesem Beispiel haben wir eine abstrakte Klasse „Beverage“ erstellt, die die Vorlagenmethode „prepareBeverage()“ für die Zubereitung von Getränken definiert. Diese Methode umfasst allgemeine Schritte wie das Kochen von Wasser und das Eingießen in Tassen, während bestimmte Schritte brew() und addCondiments() im Produktionsprozess auf Unterklassen verschoben werden. Auf diese Weise vermeiden wir, für jede bestimmte Getränkekategorie wiederholt denselben Code zum Kochen von Wasser und zum Eingießen in Tassen zu schreiben, was die Wartbarkeit und Wiederverwendbarkeit des Codes verbessert.

7. Benutzerdefinierte Anmerkungen (oder AOP-aspektorientiert)

Mithilfe des AOP-Frameworks kann gemeinsame Logik an verschiedenen Stellen eingefügt werden, wodurch die Codeduplizierung reduziert wird.

Geschäftsszene:

Angenommen, Sie entwickeln eine Webanwendung und müssen Berechtigungsprüfungen für verschiedene Controller-Methoden durchführen. Jede Controller-Methode erfordert eine ähnliche Berechtigungsüberprüfung, aber wiederholter Code führt zu Coderedundanz und Schwierigkeiten bei der Wartung.

public class MyController {
    
    
    public void viewData() {
    
    
        if (!User.hasPermission("read")) {
    
    
            throw new SecurityException("Insufficient permission to access this resource.");
        }
        // Method implementation
    }

    public void modifyData() {
    
    
        if (!User.hasPermission("write")) {
    
    
            throw new SecurityException("Insufficient permission to access this resource.");
        }
        // Method implementation
    }
}

Sie können sehen, dass bei jeder Methode, die eine Berechtigungsüberprüfung erfordert, dieselbe Berechtigungsüberprüfungslogik wiederholt geschrieben werden muss, d . Wie folgt:

@Aspect
@Component
public class PermissionAspect {
    
    

    @Before("@annotation(requiresPermission)")
    public void checkPermission(RequiresPermission requiresPermission) {
    
    
        String permission = requiresPermission.value();
        
        if (!User.hasPermission(permission)) {
    
    
            throw new SecurityException("Insufficient permission to access this resource.");
        }
    }
}

public class MyController {
    
    
    @RequiresPermission("read")
    public void viewData() {
    
    
        // Method implementation
    }

    @RequiresPermission("write")
    public void modifyData() {
    
    
        // Method implementation
    }
}

Egal wie viele Controller-Methoden auf Berechtigungen überprüft werden müssen, Sie müssen den Methoden nur die entsprechenden Anmerkungen hinzufügen. Die Logik der Berechtigungsüberprüfung wird in Aspekten zentral verwaltet, wodurch vermieden wird, dass in jeder Controller-Methode wiederholt derselbe Berechtigungsüberprüfungscode geschrieben wird. Dies verbessert die Lesbarkeit und Wartbarkeit des Codes erheblich und vermeidet Coderedundanz.

8. Funktionsschnittstellen und Lambda-Ausdrücke

Geschäftsszene:

Angenommen, Sie entwickeln eine Anwendung und müssen einen Datensatz anhand verschiedener Kriterien filtern. Die Logik jeder Filterung kann leicht unterschiedlich sein, der grundlegende Prozess ist jedoch ähnlich.

Ohne funktionale Schnittstellen und Lambda-Ausdrücke zu verwenden:

public class DataFilter {
    
    
    public List<Integer> filterPositiveNumbers(List<Integer> numbers) {
    
    
        List<Integer> result = new ArrayList<>();
        for (Integer number : numbers) {
    
    
            if (number > 0) {
    
    
                result.add(number);
            }
        }
        return result;
    }

    public List<Integer> filterEvenNumbers(List<Integer> numbers) {
    
    
        List<Integer> result = new ArrayList<>();
        for (Integer number : numbers) {
    
    
            if (number % 2 == 0) {
    
    
                result.add(number);
            }
        }
        return result;
    }
}

In diesem Beispiel haben wir zwei verschiedene Methoden zum Filtern eines Datensatzes, aber die grundlegende Schleife und die bedingte Beurteilungslogik werden wiederholt. Wir können funktionale Schnittstellen und Lambda-Ausdrücke verwenden, um doppelten Code wie folgt zu entfernen:

public class DataFilter {
    
    
    public List<Integer> filterNumbers(List<Integer> numbers, Predicate<Integer> predicate) {
    
    
        List<Integer> result = new ArrayList<>();
        for (Integer number : numbers) {
    
    
            if (predicate.test(number)) {
    
    
                result.add(number);
            }
        }
        return result;
    }
}

Wir abstrahieren die Kernlogik des Filterns. Diese Methode akzeptiert eine Predicate-Funktionsschnittstelle als Parameter zum Filtern von Daten basierend auf verschiedenen Bedingungen. Dann können wir Lambda-Ausdrücke verwenden, um bestimmte Bedingungen zu übergeben, was letztendlich dazu führt, dass doppelter Code entfernt wird.

Supongo que te gusta

Origin blog.csdn.net/weixin_39570655/article/details/132428879
Recomendado
Clasificación