﻿# Изучаем карты с исходным кодом GeoServer

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

![1161_geoserver_article_ru/image1.png](https://import.viva64.com/docx/blog/1161_geoserver_article_ru/image1.png)

## Geo вернулся

Уж не знаю, как так получилось, что сразу после написания статьи про GeoGebra я наткнулся на GeoServer\. Видимо, этот префикс оставлять меня пока не хочет\. Однако в отличие от предыдущего проекта семейства Geo \(эти проекты никак не связаны\) здесь речь пойдёт не о Geoметрии, а о Geoграфии\.

Итак, что же такое GeoServer? Это сервер\! Сервер, конечно, не простой, а такой, что обеспечивает данные и карты для клиентов вроде веб\-браузеров или геоинформационных систем\. Люди заинтересованные могут прочитать подробное описание и все его возможности на сайтах [GeoServer](https://geoserver.org/), [OSGeo](https://www.osgeo.org/projects/geoserver/) или [GeoSolutions](https://geosolutionsgroup.com/technologies/geoserver/)\. Но для того, чтобы понять, с чем имеем дело, предлагаю посмотреть интерфейс панели доступа сервера:

![1161_geoserver_article_ru/image2.png](https://import.viva64.com/docx/blog/1161_geoserver_article_ru/image2.png)

Уже на первом запуске предоставляются некоторые стартовые данные, так что можно даже визуально оценить их на карте:

![1161_geoserver_article_ru/image3.png](https://import.viva64.com/docx/blog/1161_geoserver_article_ru/image3.png)

Мы получаем в формате OpenLayers интерактивную карту мира и, кликнув по какому\-либо месту, можем получить подробности\.

Возможности сервера обрисовали\. Будь время, я бы поигрался и попробовал на открытых данных \(например, [OpenStreetMap](https://www.openstreetmap.org)\) собрать какую\-нибудь интересную карту локального места, но пока остановлюсь на этом\.

А теперь предлагаю заглянуть внутрь такого великолепного сервиса\. И не просто посмотреть, а попробовать отыскать ошибки с использованием статического анализатора PVS\-Studio\. Разворачиваем карты кода и начинаем путешествие\. 

## Опечатались

### Две модели как одна

```cpp
private GridSampleDimension[] getCoverageSampleDimensions(
    GridCoverage2DReader reader, Map<String, Serializable> customParameters)
    throws TransformException, IOException, Exception {
  ....
  ColorModel cm = imageLayout.getColorModel(null);
  if (cm == null) {
    throw new Exception(
        "Unable to acquire test coverage and color model for format:"
            + format.getName());
  }
  SampleModel sm = imageLayout.getSampleModel(null);
  if (cm == null) {
    throw new Exception(
        "Unable to acquire test coverage and sample model for format:"
            + format.getName());
  }
  ....
}
```

Создав две крайне похожие переменные _cm_ и _sm_, легко перепутать их в будущем, а также не заметить проблемы на code review\. Нетрудно понять, что здесь изначально планировали проверить _sm_ на _null_, а не проверять уже проверенное\. Исправляем опечатку и двигаемся дальше\. На ошибку здесь указывают аж два срабатывания:

* [V6007](https://pvs-studio.ru/ru/docs/warnings/v6007/) Expression 'cm \=\= null' is always false\. CatalogBuilder\.java 1242
* [V6080](https://pvs-studio.ru/ru/docs/warnings/v6080/) Consider checking for misprints\. It's possible that an assigned variable should be checked in the next condition\. CatalogBuilder\.java 1241, CatalogBuilder\.java 1242

### Необходимость нужной версии

```cpp
public StyledLayerDescriptor run(final GetStylesRequest request)
 throws ServiceException {
  if (request.getSldVer() != null
      && "".equals(request.getSldVer())
      && !"1.0.0".equals(request.getSldVer()))
    throw new ServiceException("SLD version " + request.getSldVer() +
                               " not supported");
  ....
}
```

Здесь мы наблюдаем необычную проверку\. Проверять _request\.getSldVer\(\) _одновременно на эквивалентность пустой строке и неэквивалентность _1\.0\.0_ не имеет смысла, о чём и предупреждает анализатор:

[V6057](https://pvs-studio.ru/ru/docs/warnings/v6057/) Consider inspecting this expression\. The expression is excessive or contains a misprint\. GetStyles\.java 40

Что же здесь произошло, и что должно быть?

Сообщение, которое мы должны получить в случае успешной проверки, сообщает нам о неподдерживаемой версии SLD\. Я решил посмотреть, что есть SLD, и какие версии поддерживает GeoServer\. На то и существует [документация](https://docs.geoserver.org/stable/en/user/styling/sld/reference/index.html)\. Прямо из неё узнаём:

> The OGC Styled Layer Descriptor \(SLD\) standard defines a language for expressing styling of geospatial data\. GeoServer uses SLD as its primary styling language\.

Вдаваться в подробности нет необходимости, но всё же неплохо бы уточнить что\-то про версии этого стандарта\. Дело нетрудное, смотрим немного ниже и находим:

> GeoServer implements the SLD 1\.0\.0 standard, as well as some parts of the SE 1\.1\.0 and WMS\-SLD 1\.1\.0 standards\.

Понимаем, что поддерживаются версии 1\.0\.0 и 1\.1\.0\. В нашем фрагменте кода мы действительно находим проверку на несовпадение версии 1\.0\.0, только вот необходимость эквивалентности пустой строке её ломает\. Если в запросе будет нужная нам версия, то всё прекрасно\. Но даже если версия будет, скажем, 3\.5\.2, то исключение всё равно не получим\.

Я вижу два варианта: либо проверку на пустую строку необходимо удалить в принципе, либо на её месте должна быть проверка _\!"1\.1\.0"\.equals\(request\.getSldVersion\(\)\)_, поскольку, согласно документации, эта версия стандарта частично поддерживается\.

### Высокий и широкий

Некоторые сообщения анализатора самостоятельно описывают проблему\. Например, предлагаю посмотреть следующий фрагмент кода:

```cpp
int width = w instanceof Integer ? ((Integer)w) : Integer.parseInt((String)w);
int height = w instanceof Integer ? ((Integer)h) : Integer.parseInt((String)h);
```

И, собственно, сообщение:

[V6072](https://pvs-studio.ru/ru/docs/warnings/v6072/) Two similar code fragments were found\. Perhaps, this is a typo and 'h' variable should be used instead of 'w'\. Wcs10GetCoverageRequestReader\.java 229, Wcs10GetCoverageRequestReader\.java 229, Wcs10GetCoverageRequestReader\.java 230, Wcs10GetCoverageRequestReader\.java 230

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

### Не сортировать несортируемое

```cpp
@Override
public <T extends CatalogInfo> CloseableIterator<T> list(
    final Class<T> of,
    final Filter filter,
    @Nullable Integer offset,
    @Nullable Integer count,
    @Nullable SortBy... sortOrder) {

  if (sortOrder != null) {                                // <=
    for (SortBy so : sortOrder) {
      if (sortOrder != null &&                            // <=
          !canSort(of, so.getPropertyName().getPropertyName())) {
        throw new IllegalArgumentException(
            "Can't sort objects of type "
                + of.getName()
                + " by "
                + so.getPropertyName());
      }
    }
  }
  ....
}
```

[V6007](https://pvs-studio.ru/ru/docs/warnings/v6007/) Expression 'sortOrder \!\= null' is always true\. DefaultCatalogFacade\.java 1138

Одна и та же проверка происходит дважды, причём никаких изменений по пути от одной к другой не происходит\. Очевидно, что значение второй всегда будет _true_\. Возможно, она просто лишняя?

Подозреваю, что проверить на _null_ хотели элементы _sortOrder_, следовательно, должно быть _so \!\= null_\. Ведь такая проверка защитила бы от потенциального _NullPointerException_ при использовании _so\.getPropertyName\(\)_\.

## Избегая NullPointerException

### Нужное ли проверили

```cpp
protected void updateAttributeStats(DataAttribute attribute) 
    throws IOException {
  ....
  // check we can compute min and max
  PropertyDescriptor pd = fs.getSchema().getDescriptor(attribute.getName());
  Class<?> binding = pd.getType().getBinding();
  if (pd == null
      || !Comparable.class.isAssignableFrom(binding)
      || Geometry.class.isAssignableFrom(binding)) {
    return;
  }
  ....
}
```

Что здесь подозрительного? Проверка _pd \=\= null_\. Предлагаю посмотреть предупреждение анализатора PVS\-Studio и начать расследование:

[V6060](https://pvs-studio.ru/ru/docs/warnings/v6060/) The 'pd' reference was utilized before it was verified against null\. DataPanel\.java 117 , DataPanel\.java 118

Согласитесь, странно проверять переменную _pd_ уже после её утилизации в виде _pd\.getType\(\)_\. Будь она _null_, наши руки уже были бы заняты пойманным _NullPointerException_\. Здесь у меня возникла некоторая гипотеза\.

Предлагаю посмотреть на метод _isAssignableFrom_\. Залезем в Javadocs, но я не стану приводить его целиком, ибо нас интересует лишь конкретная часть:

> @throws NullPointerException if the specified Class parameter is null

Итак, у нас есть первая зацепка: _binding_ не должен быть _null_\. Далее предлагаю посмотреть, может ли _pd\.getType\(\)\.getBinding\(\)_ вернуть _null_\. Сам метод отсылает нас к интерфейсу _PropertyType_\. Здесь мы уже находимся в зависимости, которая называется _geotools_\. И, к счастью, вновь на помощь приходят [_Javadocs_](https://docs.geotools.org/stable/javadocs/org/geotools/api/feature/type/PropertyType.html)\. Читаем про _getBindings_ и получаем:

> This value is never null\.

Что ж, от своей гипотезы отказываюсь\. Какая была гипотеза? Что проверить хотели _binding_\. 

А вот нужна ли проверка у _pd_? _fs\.getSchema\(\)_ в нашем случае возвращает _ComplexType_\. Находим _getDescriptor_, вновь [читаем](https://docs.geotools.org/stable/javadocs/org/geotools/api/feature/type/ComplexType.html) и узнаём:

> This method returns null if no such property is found\.

Прекрасно\. В получении какого\-либо значения в _pd_ действительно необходимо убедиться, однако эта проверка осуществляется слишком поздно\.

### Где мой SpringSecurityException?

```cpp
@Test
public void testChallenge() throws Exception {
  Map<String, Object> raw = getWorld();
  try {
    executeGetCoverageKvp(raw);
    fail("This should have failed with a security exception");
  } catch (Throwable e) {
    // make sure we are dealing with some security exception
    Throwable se = null;
    while (e.getCause() != null && e.getCause() != e) {
      e = e.getCause();
      if (SecurityUtils.isSecurityException(e)) {
        se = e;
      }
    }
  
    if (e == null) {                                              // <=
      fail("We should have got some sort of SpringSecurityException");
    } else {
      // some mumbling about not having enough privileges
      assertTrue(se.getMessage().contains("World"));
      assertTrue(se.getMessage().contains("privileges"));
    }
  }
}
```

Обнаруживаем себя в тесте\. Здесь происходит проверка на получение _SpringSecurityException_\. Однако PVS\-Studio находит аномалию в проверке _e \=\= null_:

[V6060](https://pvs-studio.ru/ru/docs/warnings/v6060/) The 'e' reference was utilized before it was verified against null\. ResourceAccessManagerWCSTest\.java 186, ResourceAccessManagerWCSTest\.java 193

Действительно, зачем переменную проверять на _null_, если её уже использовали? К великому счастью, код здесь обладает хорошей концентрацией комментариев и пояснительным сообщением в _fail_\. Ожидается, что мы получим исключение безопасности, а оно записывается в переменную _se_\. Эту самую переменную на _null_ и необходимо проверить\. Кстати, поскольку _e \=\= null_ никогда не вернёт _true_, то этот тест спокойно проходит\.

### Ранняя утилизация

```cpp
public CoverageViewAbstractPage(
    String workspaceName, String storeName, 
    String coverageName, CoverageInfo coverageInfo)
    throws IOException {
  ....
  // grab the coverage view
  coverageViewInfo =
      coverageInfo != null
          ? coverageInfo
          : catalog.getResourceByStore(store, coverageName, CoverageInfo.class);
  CoverageView coverageView =
      coverageViewInfo
          .getMetadata()                                                   // <=
          .get(CoverageView.COVERAGE_VIEW, CoverageView.class);
  // the type can be still not saved
  if (coverageViewInfo != null) {                                          // <=
    coverageInfoId = coverageViewInfo.getId();
  }
  if (coverageView == null) {
    throw new IllegalArgumentException(
        "The specified coverage does not have a coverage view attached to it");
  }
  ....
}
```

[V6060](https://pvs-studio.ru/ru/docs/warnings/v6060/) The 'coverageViewInfo' reference was utilized before it was verified against null\. CoverageViewAbstractPage\.java 128, CoverageViewAbstractPage\.java 132

Встретили ещё одну сомнительную проверку\. Наличие здесь проверки на _null_ вызывает вопросы\. Если ожидается, что _coverageViewInfo_ может быть _null_, то _coverageViewInfo\.getMetadata\(\)_ может выбросить _NullPointerException_\. Не похоже на опечатку, так что необходимо попробовать разобраться, что происходит\. Начнём с обращения к git blame\.

Весь код, что находится между двумя комментариями, относится к одному коммиту, остальное — к другому\. Всего в истории обнаружены два коммита\. Оригинальный выглядит так:

```cpp
public CoverageViewAbstractPage(
    String workspaceName, String storeName, 
    String coverageName, CoverageInfo coverageInfo)
    throws IOException {
  ....
  // grab the coverage view
  coverageViewInfo =
      coverageInfo != null ? coverageInfo : catalog.getResourceByStore(
      store, coverageName, CoverageInfo.class);
  CoverageView coverageView = coverageViewInfo.getMetadata().get(
      CoverageView.COVERAGE_VIEW, CoverageView.class);
  // the type can be still not saved
  if (coverageViewInfo != null) {
    coverageInfoId = coverageViewInfo.getId();
  }
  if (coverageView == null) {
    throw new IllegalArgumentException(
        "The specified coverage does not have a coverage view attached to it");
  }
  ....
}
```

В логике ничего не изменилось — проверка проводится после использования, так что потери логики из\-за слияния различных коммитов не произошло\. 

Передвижение блока с проверкой выше не имеет смысла: логика останется прежней и _coverageViewInfo\.getMetadata\(\)_ исполнится, даже если _coverageViewInfo \=\= null_\. Другой вариант — банально бросать _IllegalArgumentException_, если _coverageViewInfo \=\= null_ \(подобно проверке _coverageView_ _\=\=_ _null_\)\. Выходит, мы просто заменим одно исключение другим? Возможно\. Однако теперь к нему можно добавить пояснительное сообщение\.

## Забытые не вернулись

### Верните мою коллекцию

Вашему вниманию представляются сразу три метода, содержащие одну и ту же проблему:

```cpp
public TreeSet<Object> getTimeDomain() throws IOException {
  if (!hasTime()) {
    Collections.emptySet();
  }
  ....

  return values;
}
public TreeSet<Object> getTimeDomain(DateRange range, int maxEntries)
        throws IOException {
  if (!hasTime()) {
    Collections.emptySet();
  }
  ....

  return result;
}
public TreeSet<Object> getElevationDomain(NumberRange range, int maxEntries)
    throws IOException {
  if (!hasElevation()) {
    Collections.emptySet();
  }
  ....
  return result;
}
```

Думаю, странный вызов _emptySet_ заметили все\. Теперь же посмотрим непосредственно на сообщения анализатора PVS\-Studio:

* [V6010](https://pvs-studio.ru/ru/docs/warnings/v6010/) The return value of function 'emptySet' is required to be utilized\. ReaderDimensionsAccessor\.java 149
* [V6010](https://pvs-studio.ru/ru/docs/warnings/v6010/) The return value of function 'emptySet' is required to be utilized\. ReaderDimensionsAccessor\.java 173  
* [V6010](https://pvs-studio.ru/ru/docs/warnings/v6010/) The return value of function 'emptySet' is required to be utilized\. ReaderDimensionsAccessor\.java 324

Что же здесь должно быть? Очевидная мысль: хотели вернуть пустой _Set_, вот только _return_ не указали\. К слову, поскольку метод возвращает конкретно _TreeSet_, а не _Set_, то воспользоваться _Collections\.emptySet\(\)_ не получится, и необходимо использовать _new TreeSet<Object\>\(\)_\.

### Создали и забыли

```cpp
public CoverageViewEditor(
    String id,
    final IModel<List<String>> inputCoverages,
    final IModel<List<CoverageBand>> bands,
    IModel<EnvelopeCompositionType> envelopeCompositionType,
    IModel<SelectedResolution> selectedResolution,
    IModel<String> resolutionReferenceCoverage,
    List<String> availableCoverages) {
  ....
  coveragesChoice.setOutputMarkupId(true);
  add(coveragesChoice);

  new ArrayList<CoverageBand>();             // <=
  outputBandsChoice =
      new ListMultipleChoice<>(
          "outputBandsChoice",
          new Model<>(),
          new ArrayList<>(outputBands.getObject()),
          new ChoiceRenderer<CoverageBand>() {
            @Override
            public Object getDisplayValue(CoverageBand vcb) {
              return vcb.getDefinition();
            }
          });
  outputBandsChoice.setOutputMarkupId(true);
  add(outputBandsChoice);
  ....
}
```

Конструктор большой, но нас интересует _new ArrayList<CoverageBand\>\(\)_:

[V6010](https://pvs-studio.ru/ru/docs/warnings/v6010/) The return value of function 'new ArrayList' is required to be utilized\. CoverageViewEditor\.java 85

Зачем оно здесь? Не знаю\. Никакого эффекта не вызывает\. Возможно, было забыто присвоение переменной\. А возможно, эту строку необходимо просто удалить\. Всякое случается\.

## Сравнив идентичное

Взглянем на другой тест\. Здесь никаких исключений не ловим, и в целом всё заметно проще\. Даже слишком\.

```cpp
@Test
public void testStore() {
  Properties newProps = dao.toProperties();

  // properties equality does not seem to work...
  Assert.assertEquals(newProps.size(), props.size());
  for (Object key : newProps.keySet()) {
    Object newValue = newProps.get(key);
    Object oldValue = newProps.get(key);
    Assert.assertEquals(newValue, oldValue);
  }
```

\}

Хороший комментарий\. Не знаю, поможем ли мы пролить свет на возникшее сомнение, но всё же попробуем\. Предупреждение анализатора PVS\-Studio:

[V6027](https://pvs-studio.ru/ru/docs/warnings/v6027/) Variables 'newValue', 'oldValue' are initialized through the call to the same function\. It's probably an error or un\-optimized code\. DataAccessRuleDAOTest\.java 110, DataAccessRuleDAOTest\.java 111

Находим два _assertEquals_, один из которых сравнивает размеры _newProps_ и _props_\. Второй же сравнивает, являются ли значения одинаковыми\. Откуда берём значения? Из одних и тех же свойств\. Можно предположить, что здесь тестируется то, что _get_ возвращает одно и то же значение\. Наличие первого сравнения и интересный комментарий намекают, что, скорее всего, _oldValue_ следует присваивать результат _props\.get\(key\)_\.

## Приоритет тернарника

```cpp
@Override
public void encode(Object o) throws IllegalArgumentException {
  if (!(o instanceof GetCapabilitiesType)) {
    throw new IllegalArgumentException(
        "Not a GetCapabilitiesType: " + o != null ? o.toString() : "null");
  }
  ....
}
```

Что же не так в этом простом фрагменте? Тернарный оператор\! На самом деле, всё выглядит хорошо и идея ясна: проверяем _o_ на _null_ и возвращаем либо его строковое представление, либо литерал "_null"_\. В реальности же на _null_ будет проверяться _"Not a GetCapabilitiesType: " \+ o_\. А результат всегда будет _true_\. Сообщение анализатора:

[V6007](https://pvs-studio.ru/ru/docs/warnings/v6007/) Expression '"Not a GetCapabilitiesType: " \+ o \!\= null' is always true\. WCS20GetCapabilitiesTransformer\.java 183

Для исправления достаточно включить _o \!\= null ? o\.toString\(\) : "null"_ в скобки\.

## Ветвления нет

### Покажите путь

```cpp
/** Utility method to dump a single component/page to standard output */
public static void print(Component c, boolean dumpClass, 
                         boolean dumpValue, boolean dumpPath
) {
  WicketHierarchyPrinter printer = new WicketHierarchyPrinter();
  printer.setPathDumpEnabled(dumpClass);                         // <=
  printer.setClassDumpEnabled(dumpClass);
  printer.setValueDumpEnabled(dumpValue);
  if (c instanceof Page) {
    printer.print(c);
  } else {
    printer.print(c);
  }
}
```

Здесь возникают сразу два вопроса\. Предлагаю начать с неиспользуемого аргумента метода\. Предупреждение анализатора PVS\-Studio:

[V6022](https://pvs-studio.ru/ru/docs/warnings/v6022/) Parameter 'dumpPath' is not used inside method body\. WicketHierarchyPrinter\.java 35

Обычно для стороннего наблюдателя крайне трудно определить, является ли это ошибкой или остатками рефакторинга, а может ничего криминального и не случилось\. Однако здесь можно легко обнаружить передачу в _setPathDumpEnabled_ аргумента _dumpClass_\. Хотя я бы ожидал там _dumpPath_\.

Другое место с возможной ошибкой:

[V6004](https://pvs-studio.ru/ru/docs/warnings/v6004/) The 'then' statement is equivalent to the 'else' statement\. WicketHierarchyPrinter\.java 40, WicketHierarchyPrinter\.java 42

Здесь же предположить, какие изменения необходимо внести, оказалось несколько затруднительно\.

### Null или не Null — разницы нет

Теперь предлагаю посмотреть на следующий метод:

```cpp
public static String getMessage(Component c, Exception e) {
  if (e instanceof ValidationException) {
    ValidationException ve = (ValidationException) e;
    try {
      if (ve.getParameters() == null) {
        return new ParamResourceModel(ve.getKey(), c, 
                                      ve.getParameters()).getString();
      } else {
        return new ParamResourceModel(ve.getKey(), c, 
                                      ve.getParameters()).getString();
      }
    } catch (Exception ex) {
      LOGGER.log(Level.FINE, "i18n not found, proceeding with default message", 
                 ex);
    }
  }
  // just use the message or the toString instead
  return e.getMessage() == null ? e.toString() : e.getMessage();
}
```

В глаза бросаются идентичные тела ветвлений, анализатор PVS\-Studio это также обнаруживает:

[V6004](https://pvs-studio.ru/ru/docs/warnings/v6004/) The 'then' statement is equivalent to the 'else' statement\. GeoServerApplication\.java 116, GeoServerApplication\.java 118

Масла в огонь подозрений подливает то, что проверяемый на _null_ результат _ve\.getParameters\(\)_ передаётся в конструктор в обоих случаях\. Зачем тогда осуществлялась проверка?

### Без сглаживания или без сглаживания

```cpp
public RenderedImageMap produceMap(final WMSMapContent mapContent, 
                                   final boolean tiled)
    throws ServiceException {
  ....
  if (AA_NONE.equals(antialias)) {             // <=
    potentialPalette = mapContent.getPalette();
  } else if (AA_NONE.equals(antialias)) {      // <=
    PaletteExtractor pe = new PaletteExtractor(transparent ? null : bgColor);
    List<Layer> layers = mapContent.layers();
    for (Layer layer : layers) {
      pe.visit(layer.getStyle());
      if (!pe.canComputePalette()) break;
    }
    if (pe.canComputePalette()) potentialPalette = pe.getPalette();
  }
  ....
}
```

Мы сталкивались с одинаковыми телами в _if_/_else_, но теперь нашли одинаковые условия\. На это и ловим предупреждение анализатора:

[V6003](https://pvs-studio.ru/ru/docs/warnings/v6003/) The use of 'if \(A\) \{\.\.\.\} else if \(A\) \{\.\.\.\}' pattern was detected\. There is a probability of logical error presence\. RenderedImageMapOutputFormat\.java 240, RenderedImageMapOutputFormat\.java 242

Чтобы разобраться в происходящем, предлагаю посмотреть, какие есть альтернативы у _AA\_NONE_:

```cpp
// antialiasing settings, no antialias, only text, full antialias
private static final String AA_NONE = "NONE";
private static final String AA_TEXT = "TEXT";
private static final String AA_FULL = "FULL";
```

Выбора немного, и без углубления чутьё подсказывает, что в _else if_ предполагалось сравнение _antialias_ с _AA\_FULL_\.

## Немного чистого кода

Не так давно мы писали о том, как анализатор [подталкивает писать чистый код](https://pvs-studio.ru/ru/blog/posts/cpp/1115/)\. Тогда это рассматривали на примере C\+\+, но теперь мы можем посмотреть аналогичное на примере Java\-проекта\. Предлагаю к рассмотрению два срабатывания\.

### Исключение без условий

Начнём с выброшенного исключения в цикле без условий\.

```cpp
@Override
public void remove(StyleInfo style) {
  // ensure no references to the style
  for (LayerInfo l : facade.getLayers(style)) {
    throw new IllegalArgumentException(
        "Unable to delete style referenced by '" + l.getName() + "'"
    );
  }
  ....
}
```

Ровно на то, что я написал, и ругается анализатор PVS\-Studio:

[V6037](https://pvs-studio.ru/ru/docs/warnings/v6037/) An unconditional 'throw' within a loop\. CatalogImpl\.java 1711

Если немного подумать, то идея понятна — выбросить исключение с указанием имени слоя в сообщении\. Если _façade\.getLayers\(style\)_ вернёт пустой список, то исключение не будет выброшено\.

Я предлагаю заменить это на:

```cpp
facade.getLayers(style).stream().findFirst().ifPresent(layerInfo -> { 
  throw new IllegalArgumentException(
    "Unable to delete style referenced by '" + layerInfo.getName() + "'"
  );
});
```

Конечно, количество строк кода не изменилось, однако теперь не придётся прибегать к необычному использованию _foreach_\. А также такой вариант вполне сам себя описывает\.

Какой вариант вам нравится больше?

### Сравнение классов по имени

```cpp
static {
  try {
    NON_FEATURE_TYPE_PROXY =
        Class.forName("org.geotools.data.complex.config.NonFeatureTypeProxy");
  } catch (ClassNotFoundException e) {
    // might be ok if the app-schema datastore is not around
    if (StreamSupport.stream(
            Spliterators.spliteratorUnknownSize(
                DataStoreFinder.getAllDataStores(), Spliterator.ORDERED),
            false)
        .anyMatch(f -> 
                  f != null &&
                  f.getClass().getSimpleName()
                   .equals("AppSchemaDataAccessFactory"))) {     // <=
      LOGGER.log(
          Level.FINE,
          "Could not find NonFeatureTypeProxy yet App-schema" + 
          "is around, probably the class changed name," +
          "package or does not exist anymore",
          e);
    }
    NON_FEATURE_TYPE_PROXY = null;
  }
}
```

Вот в таком статическом блоке мы получаем срабатывание диагностики [V6054](https://pvs-studio.ru/ru/docs/warnings/v6054/):

[V6054](https://pvs-studio.ru/ru/docs/warnings/v6054/) Classes should not be compared by their name\. DefaultComplexGeoJsonWriterOptions\.java 41

Возможна ли здесь ошибка из\-за такого сравнения? Вряд ли\. Однако можно слегка упростить код и использовать _f\.getClass\(\)\.equals\(AppSchemaDataAccessFactory\.class\)_\. Признаюсь, я не смог найти этот класс в зависимостях, хотя в [документации](https://docs.geotools.org/stable/javadocs/org/geotools/data/complex/AppSchemaDataAccessFactory.html) он не указан как deprecated\. Возможно, что необходимой зависимости просто нет\. В любом случае на подобные вызовы методов следует обращать внимание и по возможности использовать лучшие практики\.

## Заключение

Проверять код в больших проектах вручную — миссия невыполнимая\. Кроме того, даже проверив глазами отдельные коммиты, можно спокойно пропустить опечатки или малоизвестные ошибки\. Именно в таких случаях на помощь приходят статические анализаторы кода вроде PVS\-Studio, который проверяет весь исходный код\. А [попробовать](https://pvs-studio.ru/ru/pvs-studio/try-free/) его можно совершенно бесплатно\!