Стандарт кодирования с несколькими условиями if
Мне нужно установить данные в сущности на основе некоторых условий.
Я использовал ниже для установки данных
if (StringUtils.isNotBlank(customerVO.getGender())) {
mstCustomer.setGender(customerVO.getGender());
}
if (StringUtils.isNotBlank(customerVO.getBirthDate())) {
mstCustomer.setDob(DateUtils.getUtilDate(customerVO.getBirthDate()));
}
if (StringUtils.isNotBlank(customerVO.getAdd1())) {
mstCustomer.setAddress1(customerVO.getAdd1());
}
if (StringUtils.isNotBlank(customerVO.getAdd2())) {
mstCustomer.setAddress2(customerVO.getAdd2());
}
if (StringUtils.isNotBlank(customerVO.getAdd3())) {
mstCustomer.setAddress3(customerVO.getAdd3());
}
if (StringUtils.isNotBlank(customerVO.getPincode())) {
mstCustomer.setPinCode(customerVO.getPincode());
}
if (StringUtils.isNotBlank(customerVO.getStateName())) {
MstState state = mstStateRepository.findByName(customerVO.getStateName());
mstCustomer.setMstState(state);
}
if (StringUtils.isNotBlank(customerVO.getCity())) {
MstCity city = mstCityRepository.findByName(customerVO.getCity());
mstCustomer.setMstCity(city);
}
if (StringUtils.isNotBlank(customerVO.getIdentificationType())) {
mstCustomer.setIdentificationType(customerVO.getIdentificationType());
}
if (StringUtils.isNotBlank(customerVO.getIdentificationData())) {
mstCustomer.setIdentificationData(customerVO.getIdentificationData());
}
MstStatus mstStatus = mstStatusRepository.findOne(MstStatusEnum.CUST_ACTIVE.getStatusCode());
if (mstStatus != null) {
mstCustomer.setMstStatus(mstStatus);
}
if (!StringUtils.isBlank(customerVO.getMaritalStatus())) {
mstCustomer.setMaritalStatus(customerVO.getMaritalStatus());
}
if (StringUtils.isBlank(customerVO.getWeddingAnniversary())) {
mstCustomer.setWeddingAnniversary(DateUtils.getDateFromString(customerVO.getWeddingAnniversary()));
}
if (StringUtils.isNotBlank(customerVO.getMotherTongue())) {
mstCustomer.setMotherTongue(customerVO.getMotherTongue());
}
if (StringUtils.isNotBlank(customerVO.getFamilySize())) {
mstCustomer.setFamilySize(Integer.valueOf(customerVO.getFamilySize()));
}
if (StringUtils.isNotBlank(customerVO.getAdultsSize())) {
mstCustomer.setAdultsSize(Integer.valueOf(customerVO.getAdultsSize()));
}
if (StringUtils.isNotBlank(customerVO.getNoOfKids())) {
mstCustomer.setNoOfKids(Integer.valueOf(customerVO.getNoOfKids()));
}
if (StringUtils.isNotBlank(customerVO.getChilddob1())) {
mstCustomer.setChilddob1(DateUtils.getDateFromString(customerVO.getChilddob1()));
}
if (StringUtils.isNotBlank(customerVO.getChilddob2())) {
mstCustomer.setChilddob2(DateUtils.getDateFromString(customerVO.getChilddob2()));
}
if (StringUtils.isNotBlank(customerVO.getProfession())) {
mstCustomer.setProfession(customerVO.getProfession());
}
Но сонар выбрасывает это исключение: Refactor this method to reduce its Cognitive Complexity from 27 to the 15 allowed.
Пожалуйста, представьте, какой способ лучше всего переработать над кодом.
Ответы
Ответ 1
Кажется довольно выполнимым, используя lambdas:
private void setIfNotBlank(String value, Consumer<String> setter) {
setConditionally(value, setter, StringUtils::isNotBlank);
}
// if you don't need non-string arguments you can inline this method
private <T> void setConditionally(T value, Consumer<T> setter, Predicate<T> shouldSet) {
if (shouldSet.test(value)) setter.accept(value);
}
Тогда
if (StringUtils.isNotBlank(customerVO.getBirthDate())) {
mstCustomer.setDob(DateUtils.getUtilDate(customerVO.getBirthDate()));
}
if (StringUtils.isNotBlank(customerVO.getCity())) {
MstCity city = mstCityRepository.findByName(customerVO.getCity());
mstCustomer.setMstCity(city);
}
станет
setIfNotBlank(customerVO.getBirthDate(), birthDate -> mstCustomer.setDob(DateUtils.getUtilDate(birthDate)));
setIfNotBlank(customerVO.getCity(), cityName -> {
MstCity city = mstCityRepository.findByName(cityName);
mstCustomer.setMstCity(city);
});
Ответ 2
Это идеально подходит для Optional
. Сначала создайте вспомогательный метод для преобразования каждого из возможных полей в Optional<String>
.
Optional<String> optional(String value) {
return StringUtils.isNotBlank(value)
? Optional.of(value)
: Optional.empty();
}
Затем перепишите код так:
optional(customerVO.getGender()) .ifPresent(mstCustomer::setGender);
optional(customerVO.getBirthDate()).map(DateUtils::getUtilDate)
.ifPresent(mstCustomer::setDob);
optional(customerVO.getAdd1()) .ifPresent(mstCustomer::setAddress1);
optional(customerVO.getAdd2()) .ifPresent(mstCustomer::setAddress2);
optional(customerVO.getAdd3()) .ifPresent(mstCustomer::setAddress3);
optional(customerVO.getPincode()) .ifPresent(mstCustomer::setPinCode);
optional(customerVO.getStateName()).map(mstStateRepository::findByName)
.ifPresent(mstCustomer::setMstState);
optional(customerVO.getCity()) .map(mstCityRepository::findByName)
.ifPresent(mstCustomer::setMstCity);
optional(customerVO.getIdentificationType()).ifPresent(mstCustomer::setIdentificationType);
optional(customerVO.getIdentificationData()).ifPresent(mstCustomer::setIdentificationData);
Optional.of(MstStatusEnum.CUST_ACTIVE.getStatusCode())
.map(mstStatusRepository::findOne)
.ifPresent(mstCustomer::setMstStatus);
optional(customerVO.getMaritalStatus()) .ifPresent(mstCustomer::setMaritalStatus);
optional(customerVO.getWeddingAnniversary()).map(DateUtils::getDateFromString)
.ifPresent(mstCustomer::setWeddingAnniversary);
optional(customerVO.getMotherTongue()).ifPresent(mstCustomer::setMotherTongue);
optional(customerVO.getFamilySize()) .map(Integer::valueOf).ifPresent(mstCustomer::setFamilySize);
optional(customerVO.getAdultsSize()) .map(Integer::valueOf).ifPresent(mstCustomer::setAdultsSize);
optional(customerVO.getNoOfKids()) .map(Integer::valueOf).ifPresent(mstCustomer::setNoOfKids);
optional(customerVO.getChilddob1()) .map(DateUtils::getDateFromString).ifPresent(mstCustomer::setChilddob1);
optional(customerVO.getChilddob2()) .map(DateUtils::getDateFromString).ifPresent(mstCustomer::setChilddob2);
optional(customerVO.getProfession()) .ifPresent(mstCustomer::setProfession);
ifPresent
вызывает именованную функцию только в том случае, если поле не пустое. map
помогает преобразовывать значения из одного типа в другой. Обратите внимание, как это помогает сгладить логику, поэтому сопоставления не зависят от настроек.
Ответ 3
По-моему, этот код принципиально не может быть улучшен. По самой своей природе он утомительный и повторяющийся. Он переводит свойства одного объекта в свойства другого объекта, и Java просто не предоставляет короткий и сжатый способ выразить его.
Рассмотрите другие ответы: они превращают один оператор if в ваш исходный код в какой-то другой оператор, поэтому вместо 20 if
вы получаете 20 optional()
вызовов или 20 setIfNotBlank()
вызовов, но основная проблема вашего кода состоит в том, что есть 20 длинных, похожих на вид утверждений, поэтому они не улучшаются значительно, вы по-прежнему заканчиваете 20 длинными аналогичными заявлениями в одном методе.
Вы можете обратиться к размышлению: придумать некоторые аннотации и аннотировать классы объектов customerVO
и mstCustomer
с ними, а затем переписать этот метод в жилах "Для каждой аннотации к первому объекту найдите соответствующий аннотация для второго объекта, выполнить присвоение". Но теперь вместо 20 длинных аналогичных выглядящих if
-statements у вас есть 40 длинных похожих похожих аннотаций, и они находятся в двух разных файлах. Это еще хуже, поэтому, если вы решили пойти этим путем, попробуйте аннотировать только один из классов, а не оба из них: тогда вы закончите только 20 аннотаций, чтобы все было так плохо, как раньше.
Вы можете перейти к генерации кода: придумайте какой-нибудь простой формат описания свойств (или найдите существующий), напишите небольшой инструмент, который примет это описание и выдает код для классов объектов customerVO
и mstCustomer
, Теперь вместо 20 длинных аналогичных выглядящих if
-statements у вас есть 20, надеюсь, коротких, может быть, не очень похожих на описание свойств на языке, который вы знаете, и дополнительном инструменте в цепочке сборки.
Как вы можете видеть, вы просто не можете избежать написания 20 длинных скучных похожих вещей.
Итак, я бы ответил: не рефакторируйте его. Отметьте этот код и классы объектов customerVO
и mstCustomer
, как подключенные, и убедитесь, что они всегда меняют их в синхронизации. Это не означает написания некоторых модульных тестов для этого - хотя вы тоже должны это сделать - это означает создание процедуры. Это уже не техническая проблема: это проблема людей. Расскажите другим разработчикам об этом фрагменте кода; пишите комментарии "Не забудьте проверить другие части" в этом коде; просмотрите коммиты с изменениями в тех частях кода. Вы не можете полагаться на средства автоматизации, чтобы этот код был правильным, поэтому вы и ваши коллеги должны помнить, что нужно делать это вручную.
Ответ 4
Самый простой способ решить эту проблему можно было бы удобно, написав сеттеры для всех этих назначений.
Вроде:
private void setGender(GenderObject customerVO){
if (StringUtils.isNotBlank(customerVO.getGender())) {
this.setGender(customerVO.getGender());
}
}
Ответ 5
Если вы можете вызвать сеттер в любом случае, вы можете упростить код, заручив ненулевое обращение на методы повторного использования:
mstCustomer.setGender(parseString(customerVO.getGender());
mstCustomer.setDob(parseDate(customerVO.getDob());
...
где
String parseString(String s) {
return StringUtils.isBlank(s) ? null : s;
}
и т.д.
Независимо от выбранного вами подхода, устранение избыточных условий будет благом для удобства и правильности.
Я действительно имею в виду это, и все доказательства, которые мне нужны, находятся в вашем существующем коде:
if (!StringUtils.isBlank(customerVO.getMaritalStatus())) {
mstCustomer.setMaritalStatus(customerVO.getMaritalStatus());
}
if (StringUtils.isBlank(customerVO.getWeddingAnniversary())) {
mstCustomer.setWeddingAnniversary(DateUtils.getDateFromString(customerVO.getWeddingAnniversary()));
}
if (StringUtils.isNotBlank(customerVO.getMotherTongue())) {
mstCustomer.setMotherTongue(customerVO.getMotherTongue());
}
См. несогласованные условные обозначения? Первый и последний случай имеют смысл, но годовщина свадьбы устанавливается только в том случае, если годовщина свадьбы не указана!
Скопировав вставку и смешивая два разных варианта логики обработки нулей, была введена ошибка, и, поскольку никто не достаточно терпелив, чтобы действительно прочитать это много повторения, никогда не замечал.
Вкратце, не дублируйте код. Даже для чего-то вроде тривиального, как нулевое обращение.