JavaRush /Java Blog /Random-KO /NULL이 왜 나쁜가요?
Helga
레벨 26

NULL이 왜 나쁜가요?

Random-KO 그룹에 게시되었습니다

NULL이 왜 나쁜가요?

다음은 Java에서 NULL을 사용하는 간단한 예입니다. public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return null; } return new Employee(id); } 이 방법에 어떤 문제가 있나요? 객체 대신 NULL을 반환할 수 있습니다. 이것이 잘못된 것입니다. NULL을 사용하는 것은 OOP에서 끔찍한 관행이므로 어떤 대가를 치르더라도 피해야 합니다. Tony Hoare의 프레젠테이션 "Zero Links: A Billion Dollar Mistake" 와 David West의 저서 "Object-Oriented Thinking" 을 포함하여 이 문제에 대해 이미 다양한 의견이 발표되었습니다 . 여기에서는 모든 주장을 요약하고 NULL을 적절한 객체 지향 구성으로 대체하여 사용을 피할 수 있는 방법의 예를 보여 드리겠습니다. 먼저 NULL에 대한 두 가지 가능한 대안을 살펴보겠습니다. 첫 번째는 Null 객체 디자인 패턴입니다 (상수로 가장 잘 구현됨). public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return Employee.NOBODY; } return Employee(id); } 두 번째 가능한 대안은 객체를 반환할 수 없는 경우 예외를 던져 "빠른 실패"를 public Employee getByName(String name) { int id = database.find(name); if (id == 0) { throw new EmployeeNotFoundException(name); } return Employee(id); } 하는 것입니다. 이제 이 글을 작성하기 전에 NULL 사용에 반대하는 주장을 살펴보겠습니다. 그 게시물을 통해 나는 위에서 언급한 Tony Hoare의 프레젠테이션과 David West의 책 외에도 여러 출판물을 알게 되었습니다. Robert Martin의 "Clean Code" , Steve McConnell의 "Clean Code" , John Sonmez 의 "Say No to NULL" , StackOverflow에 대한 "Is Returning NULL a Bad Practice?"라는 토론이 있습니다.
수동으로 오류 처리
객체를 입력으로 받을 때마다 그것이 실제 객체에 대한 참조인지 NULL인지 확인해야 합니다. 확인하는 것을 잊어버리면 NPE( NullPointerExeption ) 가 발생하여 실행 중에 프로그램이 중단될 수 있습니다 . 이로 인해 코드는 수많은 검사와 if/then/else 분기로 채워지기 시작합니다. 이것이 C 및 기타 엄격한 절차적 프로그래밍 언어 // 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); } 에서 예외를 처리하는 방법입니다 . OOP에서는 주로 수동으로 작성된 처리 블록을 제거하기 위해 예외 처리가 도입되었습니다. OOP에서는 예외가 애플리케이션 전체의 오류 처리기에 도달할 때까지 버블링되도록 허용하므로 코드가 훨씬 깔끔하고 짧아집니다. NULL 참조를 절차적 프로그래밍 스타일의 유물로 간주하고 대신 1) Null 개체 또는 2) 예외를 사용합니다. . dept.getByName("Jeffrey").transferTo(dept2);
모호한 이해
Whatбы точно передать в названии смысл происходящего, метод getByName() должен быть переименован в getByNameOrNullIfNotFound(). То же самое нужно сделать для каждого метода, который возвращает an object or NULL, иначе при чтении codeа не избежать неоднозначности. Таким образом, для того, чтобы названия методов были точны, вы должны давать методам более длинные имена. Whatбы избежать неоднозначности всегда возвращайте реальный an object, нулевой an object or выбрасывайте исключение. Кто-то может возразить, что иногда нам просто необходимо возвратить NULL чтобы добиться нужного результата. Например, метод get() интерфейса Map в Java возвращает NULL, когда в Map нет больше an objectов. 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 Очевидно, что этот метод в два раза медленнее, чем исходный. What же делать? В интерфейсе Map (без намерения обидеть разработчиков) есть недостаток проектирования. Его метод get() должен был бы возвращать Iterator, и тогда наш code выглядел бы так: Iterator found = Map.search("Jeffrey"); if (!found.hasNext()) { throw new EmployeeNotFoundException(); } return found.next(); Кстати, именно так спроектирован метод STL map::find() в С++.
Компьютерное мышление против an objectно-ориентированного
Строка codeа if (employee == null) вполне понятна тому, кто знает, что an object в Java – это указатель на структуру данных, а NULL – это указатель на ничто (в процессорах Intel x86 – 0x00000000). Однако если вы начнете мыслить в an objectном стиле, эта строка становится намного менее осмысленной. Вот How наш code выглядит с an objectной точки зрения:
- Здравствуйте, это отдел разработки ПО? - Да. - Будьте добры, пригласите к телефону вашего сотрудника Джефри. - Подождите minutesку... - Здравствуйте. - Вы NULL?
Последний вопрос звучит немного странно, не так ли? Если instead of этого после вашей просьбы пригласить к телефону Джефри на том конце просто повесят трубку, это вызовет для нас определенные сложности (Исключение). В этом случае мы можем попробовать перезвонить or же доложим нашему начальнику о том, что мы не смогли поговорить с Джефри, и завершим свою основную задачу. Кроме этого, на той стороне вам могут предложить поговорить с другим человеком, который, хоть и не является Джефри, может либо помочь вам с большинством ваших вопросов, либо отказаться помогать, если нам нужно узнать что-то, что знает только Джефри (Нулевой Объект).
Медленный провал
Вместо быстрого завершения работы, code выше пытается умереть медленно, убивая других на своем пути. Вместо того, чтобы дать всем понять, что что-то пошло не так и нужно немедленно начинать обработку исключительного события, он пытается скрыть свой провал от клиента. Это очень похоже на ручную обработку исключений, о которой мы говорor выше. Делать свой code How можно более хрупким и позволять ему прерываться, если это нужно – хорошая практика. Делайте свои методы предельно требовательными к данным, с которыми они работают. Позволяйте им жаловаться, выкидывая исключения, если данных, которые им предоставor, недостаточно, or же данные просто не подходят для использования в этом методе по задуманному сценарию. В противном случае возвращайте Нулевой Объект, который ведет себя Howим-то общепринятым способоы и выбрасывает исключения во всех других случаях. 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; }
Изменяемые и незавершенные an objectы
Вообще, строго рекомендуется проектировать an objectы так, чтобы они были неизменяемыми. Это значит, an object должен получить все необходимые данные при его создании и никогда не менять своего состояния в течение всего жизненного цикла. Значения NULL очень часто используются в паттерне проектирования «Ленивая загрузка» для того, чтобы сделать an objectы незавершенными и изменяемыми. Пример: public class Department { private Employee found = null; public synchronized Employee manager() { if (this.found == null) { this.found = new Employee("Jeffrey"); } return this.found; } } Несмотря на то, что эта технология широко распространена, для ООП она является антипаттерном. И главным образом потому, что заставляет an object нести ответственность за проблемы с производительностью у вычислительной платформы, а это How раз то, о чем an object Employee не может быть осведомлен. Вместо того, чтобы управлять своим состоянием и вести себя соответствующим своему предназначению образом, an object вынужден заботиться о кэшировании своих собственных результатов – вот к чему приводит «ленивая загрузка». А ведь кэширование – это вовсе не то, чем занимается сотрудник в офисе, не так ли? Выход? Не используйте «ленивую загрузку» таким примитивным способом, How в вышеприведенном примере. Вместо этого переместите кэширование проблем на другой уровень своего applications. Например, в Java вы можете использовать возможности аспектно-ориентированного программирования. Например, в jcabi-aspects есть annotation @Cacheable, которая кэширует meaning, возвращаемое методом. import com.jcabi.aspects.Cacheable; public class Department { @Cacheable(forever = true) public Employee manager() { return new Employee("Jacky Brown"); } } Надеюсь, этот анализ был достаточно убедителен, чтобы вы прекратor обNULLять свой code :) Оригинал статьи здесь. Вам также могут быть интересны такие темы How: • DI Containers are Code PollutersGetters/Setters. Evil. Period.Anti-Patterns in OOPAvoid String ConcatenationObjects Should Be Immutable
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION