Запах кода 304: Исключение с нулевым указателем - как избежать нулевых ссылок, которые вызывают сбои во время выполнения

Запах кода 304: Исключение с нулевым указателем - как избежать нулевых ссылок, которые вызывают сбои во время выполнения

21 июня 2025 г.

Я продолжаю писать о нулевых проблемах, но каждый день новость напоминает мне: NULL все еще жив и пинает.

TL; DR: Избегайте нулевых ссылок, которые вызывают сбои времени выполнения, используя надлежащую проверку и нулевые модели

Проблемы 😔

  • Время выполнениясбои
  • Большойинциденты и отключения
  • Непредсказуемое поведение
  • Тяжелая отладка
  • Пользовательский разочарование
  • Системная нестабильность
  • Плохая надежность

ВGoogle Cloudслучай:

  • Плохая обработка ошибок: код разбился вместо изящной обработки нулевых данных
  • Нетфункции флагов: Новый код не был постепенно развернут с управлением безопасности
  • Мгновенная глобальная репликация: плохие данные распространяются по всему миру, как вШоудстрик инцидент
  • Без рандомизированного отборока: восстановление вызвало перегрузку инфраструктуры
  • Неадекватное тестирование: сценарий сбоя никогда не проверялся во время развертывания

Решения 😃

  1. Избегатьноль
  2. Используйте NULL -проверки, если нули находятся за пределами вашего контроля (например, внешний API)
  3. Инициализировать значения по умолчанию
  4. Реализуйте охраны
  5. Использоватьнулевые объекты
  6. Не используйтеопционы

Рефакторинг ⚙

https://hackernoon.com/code-refactoring-tips-no-015-remove-null

Контекст 💬

12 июня 2025 годаОсновной отключениепроизошло на платформе Google Cloud.

Это повлияло на десятки сервисов Google Cloud и Google Workspace во всем мире примерно с 10:49 до 13:49 PDT (всего 3 часа), причем некоторые услуги занимают больше времени для полного восстановления.

Отключение было вызвано каскадным сбоем в системе управления API Google:

  • Триггер:

29 мая 2025 года Google развернул новый код в «Управление услугами» (их система управления API), в котором были добавлены дополнительные проверки политики квот.

Этот код имел критический недостаток. Ему не хваталообработка ошибоки не был защищенфункции флаговПолем

  • Неудача:

12 июня изменение политики, содержащее бланк/НУЛЕВОЙFields был направлен в глобальную базу данных, которую использует управление службой. Когда управление обслуживанием попыталось обработать эти пустые поля, он столкнулся с нулевым указателем в незащищенном пути кода, что приводит к тому, что двоичные файлы сбои в бесконечном цикле.

  • Глобальное влияние:

Поскольку управление квотами является глобальным, эти поврежденные данные были воспроизведены по всему миру в течение нескольких секунд, что привело к сбою услуг в каждом регионе.

Исключения нулевого указателя случаются, когда вы пытаетесь получить доступ к методам или свойствам на объектах, которых нет.

Это происходит, когда переменные содержат нулевые ссылки вместо допустимых экземпляров объекта.

Проблема становится особенно опасной в производственных средах, где эти исключения могут разрушить ваше приложение и расстроить пользователей.

Такие языки, как Java, C#и JavaScript, особенно склонны к этой проблеме, хотя современные языковые функции и модели могут помочь вам полностью избежать этих сбоев.

Nulls были серьезной проблемой в индустрии программного обеспечения на протяжении десятилетий, однако инженеры -программисты продолжают игнорировать их, несмотря на предупреждения своего создателя.

https://hackernoon.com/null-the-billion-dollar-mistake-8t5z32d6?embedable=true

Пример кода 📖

Неправильно ❌

public class ServiceControlPolicy {
  private SpannerDatabase spannerDB;
  private QuotaManager quotaManager;
    
  public void applyPolicyChange(PolicyChange change) {
      // NULL POINTER: change can be null
      Policy policy = spannerDB.getPolicy(change.getPolicyId());
      // NULL POINTER: policy can be null from the database
      String quotaField = policy.getQuotaField();
      // NULL POINTER: quotaField can be null (blank field)
      quotaManager.updateQuota(quotaField, change.getValue());
  }
    
  public void exerciseQuotaChecks(String region) {
      // NULL POINTER: policies list can be null
      List<Policy> policies = spannerDB.getPoliciesForRegion(region);
      for (Policy policy : policies) {
          // NULL POINTER: individual policy can be null
          String quotaValue = policy.getQuotaField();
          // NULL POINTER: quotaValue can be null before trim()
          quotaManager.checkQuota(quotaValue.trim());
      }
  }
    
  public boolean validatePolicyData(Policy policy) {
      // NULL POINTER: policy parameter can be null
      String quotaField = policy.getQuotaField();
      // NULL POINTER: quotaField can be null before length()
      return quotaField.length() > 0 && 
             !quotaField.equals("null");
  }
    
  public void replicateGlobally(PolicyChange change) {
      List<String> regions = getGlobalRegions();
      for (String region : regions) {
          // NULL POINTER: change.getPolicy() can return null
          spannerDB.insertPolicy(region, change.getPolicy());
      }
  }
}

Справа 👉

public class ServiceControlPolicy {
  private SpannerDatabase spannerDB;
  private QuotaManager quotaManager;
    
  public void applyPolicyChange(PolicyChange change) {
      if (change == null) {
          // Assuming it comes from an external API
          // Beyond your control
          change = new NullPolicyChange();
      }
      
      Policy policy = findPolicyOrNull(change.policyId());
      String quotaField = policy.quotaField();
      if (!quotaField.isEmpty()) {
          quotaManager.updateQuota(quotaField, change.value());
      }
  }
    
  public void exerciseQuotaChecks(String region) {
      if (region == null || region.isEmpty()) {
          // Assuming it comes from an external API
          // Beyond your control
          return;
      }
      
      List<Policy> policies = policiesOrEmpty(region);
      
      for (Policy policy : policies) {
          String quotaValue = policy.quotaField();
          if (!quotaValue.isEmpty()) {
              quotaManager.checkQuota(quotaValue.trim());
          }
      }
  }
    
  public boolean validatePolicyData(Policy policy) {
      if (policy == null) {
          // Assuming it comes from an external API
          // Beyond your control
          // From now on, you wrap it
          policy = new NullPolicy();
      }
      
      String quotaField = policy.quotaField();
      return quotaField.length() > 0;
  }
    
  public void replicateGlobally(PolicyChange change) {
      if (change == null) {
          // Assuming it comes from an external API
          // Beyond your control
          // From now on, you wrap it
          change = new NullPolicyChange();
      }
        
      Policy policy = change.policy();
      if (policy == null) {
          // Assuming it comes from an external API
          // Beyond your control
          // From now on, you wrap it
          policy = new NullPolicy();
      }
        
      List<String> regions = globalRegions();
      for (String region : regions) {
          spannerDB.insertPolicy(region, policy);
      }
  }
    
  private Policy findPolicyOrNull(String policyId) {
      Policy policy = spannerDB.policy(policyId);
      return policy != null ? policy : new NullPolicy();
  }
    
  private List<Policy> policiesOrEmpty(String region) {
      List<Policy> policies = spannerDB.policiesForRegion(region);
      if (policies == null) {
          // This is a good NullObject
          return Collections.emptyList();
      }
      
      return policies.stream()
              .map(p -> p != null ? p : new NullPolicy())
              .collect(Collectors.toList());
  }
}

class NullPolicy extends Policy {
  @Override
  public String quotaField() { return ""; }
    
  @Override
  public String policyId() { return "unknown-policy"; }
    
  @Override
  public Map<String, String> metadata() { 
      return Collections.emptyMap(); 
  }
}

class NullPolicyChange extends PolicyChange {
  @Override
  public String policyId() { return ""; }
    
  @Override
  public String value() { return ""; }
    
  @Override
  public Policy policy() { return new NullPolicy(); }
}

Обнаружение 🔍

  • [x]Полуавтоматический

Вы можете обнаружить потенциальные исключения из нулевого указателя, просмотрев код для прямых вызовов метода по объектам без нулевых проверок.

Линтеры могут исследовать возвратные значения по методам, которые могут вернутьНулевой, в поисках ненициализированных полей объектов и использования инструментов статического анализа, которые помечают потенциальные нулевые измерения.

Современные IDE часто выделяют эти проблемы с предупреждениями.

Теги 🏷

  • Нулевой

Уровень 🔋

  • [x]Средний

Почему биение важна 🗺

ВРеальный мир, объекты либо существуют, либо нет.

Когда вы правильно моделируете это в своей программе, вы создаете четкоеОдин-один перепискамежду реальностью и кодом.

Разрыв этой бикологии, позволяя нулевым ссылкам, создает фантомные объекты, которые существуют в вашем коде, но не в реальном мире, что приводит к сбоям, когда вы пытаетесь взаимодействовать с этими несуществующими объектами.

Если вы решите назвать свой номерной знак «Null», вы получитемного парковочных билетов

Поколение AI 🤖

Генераторы ИИ часто создают код с уязвимостями нулевых указателей, потому что они сосредоточены на сценариях счастливого пути.

Они часто генерируют вызовы методов без учета краевых случаев, когда объекты могут бытьНУЛЕВОЙ, особенно в сложных иерархиях объектов или при работе с внешними источниками данных.

Обнаружение ИИ 🧲

Инструменты искусственного интеллекта могут обнаружить и исправлять проблемы с нулевым указателем, когда вы предоставляете четкие инструкции о практике защитных программирования.

Попробуйте их! 🛠

Помните: помощники ИИ делают много ошибок

Предлагаемое подсказка: удалить все нулевые ссылки

Без надлежащих инструкций

С конкретными инструкциями

Чатгпт

Чатгпт

Клод

Клод

Недоумение

Недоумение

Второй пилот

Второй пилот

Близнецы

Близнецы

DeepSeek

DeepSeek

Meta ai

Meta ai

Грок

Грок

Qwen

Qwen

Заключение 🏁

Исключения нулевого указателя представляют собой одну из наиболее распространенных ошибок времени выполнения в программировании.

Вы можете удалить большинство из этих сбоев, внедрив правильные нулевые проверки, используя шаблон проектирования NULL объекта и приняв практики защитного программирования.

Небольшие накладные расходы кода валидации значительно окупаются при стабильности приложения и пользовательском опыте.

Отношения 👩‍❤

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-iii-t7h3zkv

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-xliii

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-xxxix

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-xxvi

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-xlii

https://hackernoon.com/code-smell-252-nullcustomer?embedable=true

https://hackernoon.com/code-smell-260-crowdstrike-null?embedable=true

Больше информации 📕

https://status.cloud.google.com/incidents/ow5i3ppk96rdumcb1ssw?embedable=true

https://www.wired.com/story/null-license-plate-ly-one-hacker-ticket-hell/?embedable=true

https://www.forbes.com/sites/zakdoffman/2019/08/14/hacker-gets-12000-in-parking-trickets-after-null-license-prate-trick-backfires/?embedable=true

Отказ от ответственности 📘

Кодовые запахи - моимнениеПолем


Я называю это своей ошибкой в ​​миллиард долларов. Это было изобретение нулевой ссылки в 1965 году

Тони Хоар

https://hackernoon.com/400-thought-предвидение-software-engineering-quotes?embedable=true


Эта статья является частью серии Codesmell.

https://hackernoon.com/how-to-find-the-shooky-parts-of-your-code-part-i-xqz3evd?embedable=true


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE