JavaRush /Blog Java /Random-ES /¿Por qué NULL es malo?
Helga
Nivel 26

¿Por qué NULL es malo?

Publicado en el grupo Random-ES

¿Por qué NULL es malo?

Aquí hay un ejemplo simple del uso de NULL en Java: ¿ public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return null; } return new Employee(id); } Qué tiene de malo este método? Puede devolver NULL en lugar de un objeto; eso es lo que está mal. Usar NULL es una práctica terrible en programación orientada a objetos y debe evitarse a toda costa. Ya se han publicado bastantes opiniones diferentes sobre este tema, incluida la presentación de Tony Hoare "Zero Links: A Billion Dollar Mistake" y el libro completo de David West "Object-Oriented Thinking". Aquí intentaré resumir todos los argumentos y mostrar ejemplos de cómo se puede evitar el uso de NULL reemplazándolo con construcciones orientadas a objetos adecuadas. Primero, veamos dos posibles alternativas a NULL. El primero es el patrón de diseño de Objeto Nulo (mejor implementado con una constante): public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return Employee.NOBODY; } return Employee(id); } La segunda alternativa posible es "fallar rápidamente" lanzando una excepción si el objeto no puede ser devuelto: public Employee getByName(String name) { int id = database.find(name); if (id == 0) { throw new EmployeeNotFoundException(name); } return Employee(id); } Ahora veamos los argumentos en contra del uso de NULL Antes de escribir esto Durante Después del post, conocí, además de las presentaciones de Tony Hoare antes mencionadas y el libro de David West, varias publicaciones. Estos son "Código limpio" de Robert Martin, "Código limpio" de Steve McConnell, "Di no a NULL" de John Sonmez y una discusión sobre StackOverflow llamada "¿Devolver NULL es una mala práctica?"
Manejar errores manualmente
Cada vez que recibe un objeto como entrada, debe verificar si es una referencia a un objeto real o un NULL. Si olvida comprobarlo, su programa puede verse interrumpido a mitad de ejecución por una NullPointerExeption (NPE) lanzada. Debido a esto, su código comienza a llenarse con numerosas comprobaciones y ramas if/then/else. // 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); } Así es como se deben manejar las excepciones en C y otros lenguajes de programación estrictamente procedimentales. En la programación orientada a objetos, el manejo de excepciones se introdujo principalmente para deshacerse de los bloques de procesamiento escritos manualmente. En POO, permitimos que las excepciones surjan hasta que alcancen el controlador de errores de toda la aplicación, y esto hace que nuestro código sea mucho más limpio y corto: dept.getByName("Jeffrey").transferTo(dept2); considere que NULL hace referencia a una reliquia del estilo de programación procedimental y use 1) Objetos nulos o 2) Excepciones en su lugar. .
Comprensión ambigua
Quéбы точно передать в названии смысл происходящего, метод getByName() должен быть переименован в getByNameOrNullIfNotFound(). То же самое нужно сделать для каждого метода, который возвращает un objeto o NULL, иначе при чтении códigoа не избежать неоднозначности. Таким образом, для того, чтобы названия методов были точны, вы должны давать методам более длинные имена. Quéбы избежать неоднозначности всегда возвращайте реальный un objeto, нулевой un objeto o выбрасывайте исключение. Кто-то может возразить, что иногда нам просто необходимо возвратить NULL чтобы добиться нужного результата. Например, метод get() интерфейса Map в Java возвращает NULL, когда в Map нет больше un objetoов. Employee employee = employees.get("Jeffrey"); if (employee == null) { throw new EmployeeNotFoundException(); } return employee; Благодаря использованию NULL в Map этому códigoу хватает всего одного цикла поиска для получения результата. Если мы перепишем Map таким образом, чтобы метод get() выбрасывал исключение в случае, если ничего не найдено, наш código будет выглядеть так: if (!employees.containsKey("Jeffrey")) { // first search throw new EmployeeNotFoundException(); } return employees.get("Jeffrey"); // second search Очевидно, что этот метод в два раза медленнее, чем исходный. Qué же делать? В интерфейсе Map (без намерения обидеть разработчиков) есть недостаток проектирования. Его метод get() должен был бы возвращать Iterator, и тогда наш código выглядел бы так: Iterator found = Map.search("Jeffrey"); if (!found.hasNext()) { throw new EmployeeNotFoundException(); } return found.next(); Кстати, именно так спроектирован метод STL map::find() в С++.
Компьютерное мышление против un objetoно-ориентированного
Строка códigoа if (employee == null) вполне понятна тому, кто знает, что un objeto в Java – это указатель на структуру данных, а NULL – это указатель на ничто (в процессорах Intel x86 – 0x00000000). Однако если вы начнете мыслить в un objetoном стиле, эта строка становится намного менее осмысленной. Вот Cómo наш código выглядит с un objetoной точки зрения:
- Здравствуйте, это отдел разработки ПО? - Да. - Будьте добры, пригласите к телефону вашего сотрудника Джефри. - Подождите minutosку... - Здравствуйте. - Вы NULL?
Последний вопрос звучит немного странно, не так ли? Если en lugar de этого после вашей просьбы пригласить к телефону Джефри на том конце просто повесят трубку, это вызовет для нас определенные сложности (Исключение). В этом случае мы можем попробовать перезвонить o же доложим нашему начальнику о том, что мы не смогли поговорить с Джефри, и завершим свою основную задачу. Кроме этого, на той стороне вам могут предложить поговорить с другим человеком, который, хоть и не является Джефри, может либо помочь вам с большинством ваших вопросов, либо отказаться помогать, если нам нужно узнать что-то, что знает только Джефри (Нулевой Объект).
Медленный провал
Вместо быстрого завершения работы, código выше пытается умереть медленно, убивая других на своем пути. Вместо того, чтобы дать всем понять, что что-то пошло не так и нужно немедленно начинать обработку исключительного события, он пытается скрыть свой провал от клиента. Это очень похоже на ручную обработку исключений, о которой мы говорo выше. Делать свой código Cómo можно более хрупким и позволять ему прерываться, если это нужно – хорошая практика. Делайте свои методы предельно требовательными к данным, с которыми они работают. Позволяйте им жаловаться, выкидывая исключения, если данных, которые им предоставo, недостаточно, o же данные просто не подходят для использования в этом методе по задуманному сценарию. В противном случае возвращайте Нулевой Объект, который ведет себя Cómoим-то общепринятым способоы и выбрасывает исключения во всех других случаях. 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; }
Изменяемые и незавершенные un objetoы
Вообще, строго рекомендуется проектировать un objetoы так, чтобы они были неизменяемыми. Это значит, un objeto должен получить все необходимые данные при его создании и никогда не менять своего состояния в течение всего жизненного цикла. Значения NULL очень часто используются в паттерне проектирования «Ленивая загрузка» для того, чтобы сделать un objetoы незавершенными и изменяемыми. Пример: public class Department { private Employee found = null; public synchronized Employee manager() { if (this.found == null) { this.found = new Employee("Jeffrey"); } return this.found; } } Несмотря на то, что эта технология широко распространена, для ООП она является антипаттерном. И главным образом потому, что заставляет un objeto нести ответственность за проблемы с производительностью у вычислительной платформы, а это Cómo раз то, о чем un objeto Employee не может быть осведомлен. Вместо того, чтобы управлять своим состоянием и вести себя соответствующим своему предназначению образом, un objeto вынужден заботиться о кэшировании своих собственных результатов – вот к чему приводит «ленивая загрузка». А ведь кэширование – это вовсе не то, чем занимается сотрудник в офисе, не так ли? Выход? Не используйте «ленивую загрузку» таким примитивным способом, Cómo в вышеприведенном примере. Вместо этого переместите кэширование проблем на другой уровень своего aplicaciones. Например, в Java вы можете использовать возможности аспектно-ориентированного программирования. Например, в jcabi-aspects есть anotación @Cacheable, которая кэширует significado, возвращаемое методом. import com.jcabi.aspects.Cacheable; public class Department { @Cacheable(forever = true) public Employee manager() { return new Employee("Jacky Brown"); } } Надеюсь, этот анализ был достаточно убедителен, чтобы вы прекратo обNULLять свой código :) Оригинал статьи здесь. Вам также могут быть интересны такие темы Cómo: • DI Containers are Code PollutersGetters/Setters. Evil. Period.Anti-Patterns in OOPAvoid String ConcatenationObjects Should Be Immutable
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION