JavaRush /Java-Blog /Random-DE /Warum ist NULL schlecht?
Helga
Level 26

Warum ist NULL schlecht?

Veröffentlicht in der Gruppe Random-DE

Warum ist NULL schlecht?

Hier ist ein einfaches Beispiel für die Verwendung von NULL in Java: public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return null; } return new Employee(id); } Was ist an dieser Methode falsch? Es kann NULL anstelle eines Objekts zurückgeben – das ist der Fehler. Die Verwendung von NULL ist eine schreckliche Praxis in OOP und sollte unbedingt vermieden werden. Zu diesem Thema wurden bereits zahlreiche unterschiedliche Meinungen veröffentlicht, darunter Tony Hoares Vortrag „Zero Links: A Billion Dollar Mistake“ und David Wests gesamtes Buch „Object-Oriented Thinking“. Hier werde ich versuchen, alle Argumente zusammenzufassen und Beispiele zu zeigen, wie Sie die Verwendung von NULL vermeiden können, indem Sie es durch geeignete objektorientierte Konstrukte ersetzen. Schauen wir uns zunächst zwei mögliche Alternativen zu NULL an. Das erste ist das Null-Objekt- Entwurfsmuster (am besten mit einer Konstante implementiert): public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return Employee.NOBODY; } return Employee(id); } Die zweite mögliche Alternative besteht darin, „schnell zu scheitern“ , indem eine Ausnahme ausgelöst wird, wenn das Objekt nicht zurückgegeben werden kann: public Employee getByName(String name) { int id = database.find(name); if (id == 0) { throw new EmployeeNotFoundException(name); } return Employee(id); } Schauen wir uns nun die Argumente gegen die Verwendung von NULL an, bevor wir dies schreiben Durch den Beitrag habe ich neben den oben genannten Vorträgen von Tony Hoare und dem Buch von David West auch eine Reihe von Veröffentlichungen kennengelernt. Dies sind „Clean Code“ von Robert Martin, „Clean Code“ von Steve McConnell, „Say No to NULL“ von John Sonmez und eine Diskussion auf StackOverflow mit dem Titel „Ist die Rückgabe von NULL eine schlechte Praxis?“
Fehler manuell behandeln
Jedes Mal, wenn Sie ein Objekt als Eingabe erhalten, müssen Sie prüfen, ob es sich um eine Referenz auf ein reales Objekt oder um einen NULL-Wert handelt. Wenn Sie die Überprüfung vergessen, wird Ihr Programm möglicherweise mitten in der Ausführung durch eine ausgelöste NullPointerException (NPE) unterbrochen. Aus diesem Grund beginnt Ihr Code mit zahlreichen Prüfungen und If/Then/Else-Verzweigungen zu füllen. // this is a terrible design, don't reuse Employee employee = dept.getByName("Jeffrey"); if (employee == null) { System.out.println("can't find an employee"); System.exit(-1); } else { employee.transferTo(dept2); } So sollten Ausnahmen in C und anderen streng prozeduralen Programmiersprachen behandelt werden. In OOP wurde die Ausnahmebehandlung hauptsächlich eingeführt, um manuell geschriebene Verarbeitungsblöcke zu beseitigen. In OOP lassen wir zu, dass Ausnahmen aufsteigen, bis sie den anwendungsweiten Fehlerbehandler erreichen, und das macht unseren Code viel sauberer und kürzer: dept.getByName("Jeffrey").transferTo(dept2); Betrachten Sie NULL-Referenzen als Relikt des prozeduralen Programmierstils und verwenden Sie stattdessen 1) Nullobjekte oder 2) Ausnahmen .
Mehrdeutiges Verständnis
Wasбы точно передать в названии смысл происходящего, метод getByName() должен быть переименован в getByNameOrNullIfNotFound(). То же самое нужно сделать для каждого метода, который возвращает ein Objekt oder NULL, иначе при чтении Codeа не избежать неоднозначности. Таким образом, для того, чтобы названия методов были точны, вы должны давать методам более длинные имена. Wasбы избежать неоднозначности всегда возвращайте реальный ein Objekt, нулевой ein Objekt oder выбрасывайте исключение. Кто-то может возразить, что иногда нам просто необходимо возвратить NULL чтобы добиться нужного результата. Например, метод get() интерфейса Map в Java возвращает NULL, когда в Map нет больше ein Objektов. Employee employee = employees.get("Jeffrey"); if (employee == null) { throw new EmployeeNotFoundException(); } return employee; Благодаря использованию NULL в Map этому Codeу хватает всего одного цикла поиска для получения результата. Если мы перепишем Map таким образом, чтобы метод get() выбрасывал исключение в случае, если ничего не найдено, наш Code будет выглядеть так: if (!employees.containsKey("Jeffrey")) { // first search throw new EmployeeNotFoundException(); } return employees.get("Jeffrey"); // second search Очевидно, что этот метод в два раза медленнее, чем исходный. Was же делать? В интерфейсе Map (без намерения обидеть разработчиков) есть недостаток проектирования. Его метод get() должен был бы возвращать Iterator, и тогда наш Code выглядел бы так: Iterator found = Map.search("Jeffrey"); if (!found.hasNext()) { throw new EmployeeNotFoundException(); } return found.next(); Кстати, именно так спроектирован метод STL map::find() в С++.
Компьютерное мышление против ein Objektно-ориентированного
Строка Codeа if (employee == null) вполне понятна тому, кто знает, что ein Objekt в Java – это указатель на структуру данных, а NULL – это указатель на ничто (в процессорах Intel x86 – 0x00000000). Однако если вы начнете мыслить в ein Objektном стиле, эта строка становится намного менее осмысленной. Вот Wie наш Code выглядит с ein Objektной точки зрения:
- Здравствуйте, это отдел разработки ПО? - Да. - Будьте добры, пригласите к телефону вашего сотрудника Джефри. - Подождите Protokollку... - Здравствуйте. - Вы NULL?
Последний вопрос звучит немного странно, не так ли? Если anstatt этого после вашей просьбы пригласить к телефону Джефри на том конце просто повесят трубку, это вызовет для нас определенные сложности (Исключение). В этом случае мы можем попробовать перезвонить oder же доложим нашему начальнику о том, что мы не смогли поговорить с Джефри, и завершим свою основную задачу. Кроме этого, на той стороне вам могут предложить поговорить с другим человеком, который, хоть и не является Джефри, может либо помочь вам с большинством ваших вопросов, либо отказаться помогать, если нам нужно узнать что-то, что знает только Джефри (Нулевой Объект).
Медленный провал
Вместо быстрого завершения работы, Code выше пытается умереть медленно, убивая других на своем пути. Вместо того, чтобы дать всем понять, что что-то пошло не так и нужно немедленно начинать обработку исключительного события, он пытается скрыть свой провал от клиента. Это очень похоже на ручную обработку исключений, о которой мы говорoder выше. Делать свой Code Wie можно более хрупким и позволять ему прерываться, если это нужно – хорошая практика. Делайте свои методы предельно требовательными к данным, с которыми они работают. Позволяйте им жаловаться, выкидывая исключения, если данных, которые им предоставoder, недостаточно, oder же данные просто не подходят для использования в этом методе по задуманному сценарию. В противном случае возвращайте Нулевой Объект, который ведет себя Wieим-то общепринятым способоы и выбрасывает исключения во всех других случаях. public Employee getByName(String name) { int id = database.find(name); Employee employee; if (id == 0) { employee = new Employee() { @Override public String name() { return "anonymous"; } @Override public void transferTo(Department dept) { throw new AnonymousEmployeeException( "I can't be transferred, I'm anonymous" ); } }; } else { employee = Employee(id); } return employee; }
Изменяемые и незавершенные ein Objektы
Вообще, строго рекомендуется проектировать ein Objektы так, чтобы они были неизменяемыми. Это значит, ein Objekt должен получить все необходимые данные при его создании и никогда не менять своего состояния в течение всего жизненного цикла. Значения NULL очень часто используются в паттерне проектирования «Ленивая загрузка» для того, чтобы сделать ein Objektы незавершенными и изменяемыми. Пример: public class Department { private Employee found = null; public synchronized Employee manager() { if (this.found == null) { this.found = new Employee("Jeffrey"); } return this.found; } } Несмотря на то, что эта технология широко распространена, для ООП она является антипаттерном. И главным образом потому, что заставляет ein Objekt нести ответственность за проблемы с производительностью у вычислительной платформы, а это Wie раз то, о чем ein Objekt Employee не может быть осведомлен. Вместо того, чтобы управлять своим состоянием и вести себя соответствующим своему предназначению образом, ein Objekt вынужден заботиться о кэшировании своих собственных результатов – вот к чему приводит «ленивая загрузка». А ведь кэширование – это вовсе не то, чем занимается сотрудник в офисе, не так ли? Выход? Не используйте «ленивую загрузку» таким примитивным способом, Wie в вышеприведенном примере. Вместо этого переместите кэширование проблем на другой уровень своего Anwendungen. Например, в Java вы можете использовать возможности аспектно-ориентированного программирования. Например, в jcabi-aspects есть Anmerkung @Cacheable, которая кэширует Bedeutung, возвращаемое методом. import com.jcabi.aspects.Cacheable; public class Department { @Cacheable(forever = true) public Employee manager() { return new Employee("Jacky Brown"); } } Надеюсь, этот анализ был достаточно убедителен, чтобы вы прекратoder обNULLять свой Code :) Оригинал статьи здесь. Вам также могут быть интересны такие темы Wie: • DI Containers are Code PollutersGetters/Setters. Evil. Period.Anti-Patterns in OOPAvoid String ConcatenationObjects Should Be Immutable
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION