Вчера увидел интересную ошибку или даже баг в коде и меня это побудило на то что бы написать немного об этом.
История:
Мы пишем много кода на Груви ( Groovy ) и как это бывает в любом языке по ходу написания допускаешь ошибки. На этот раз мы откопали ошибку в коде, которая была там походу долгое время, но не кто особо не обращал внимания, так как ошибка походу себя не проявляла да и была логическая, связанная с Груви. Для того что бы объяснить и показать что я имею ввиду, думаю приведу контраст между Груви и Жавой ( Java ).
Прелюдие:
Думаю все кто пишут на груви любят то что можно “изъясниться” кратко, в особенности по сравнению с жавой. Например посмотрим на класса “человек” написанный на жаве:
/** Java Code */
public class Person {
private String firstName;
private String lastName;public String getFirstName() {
return firstName;
}public void setFirstName(String firstName) {
this.firstName = firstName;
}public String getLastName() {
return lastName;
}public void setLastName(String lastName) {
this.lastName = lastName;
}}
А теперь посмотрим на тот же класс написанный на друви:
/** Groovy Code */
class Person {
String firstName
String lastName
}
Думаю что не заметить разницы просто не возможно. Говорят что “краткость сестра таланта”, но в случае груви это приходит с определенной ценой.
Ошибка:
Я не буду в даваться в подробности груви, а сразу прыгну к проблеме. Груви динамический язык и это приносит много приятностей, но так же и требований. Вот пример бага который мы нашли в коде:
def list = [“Hello”, null, 2, false, “End”, “”, 0, “Me?”, true, 100]
def flist = []
def elist = []
list.each {
if(it) {
flist << it } else { elist << it } } println "flist:" flist.each { println "$it" } println "\nelist:" elist.each { println "$it" }
Логика данного кода такова:
У нас есть лист вещей, и мы его сортируем по критерии: “если вещь что-то из себя представляет, то есть не пуста то суем это в flist, если нет то в elist.
Вывод будет:
flist:
Hello
2
End
Me?
true
100elist:
null
false0
Что не верно, так как мы хотим получить все данные которые из себя что-то представляют. То есть “false” – это тоже даные как и ноль “0”, но вполне логично эти два значения попали в лист elist. В итоге тут логическая ошибка, так как false и 0 должны попасть в flist, а все происходит из-за динамичности и краткости груви. То есть груви на лету присваивает объекту определенный класс типо String или Integer и в соответствии с этим делает вывод при запросе в “if(it)”. То есть если у нас есть “Hello” то при эвалюации if(“Hello”) если значение “Hello” ( то есть String переменной ) не пустое ( “Hello” ) то мы получим ответ true, а если пустое ( “” ) то получим false. Но тот же самый код будет не верно определять если объект будет номером или буленом ( boolean ). То есть если мы спросим if(0) то тут же получим false, – а это противоположная реакция примеру с текстом. Но если мы спросим if(1) то получим true – то есть все что кроме 0 это правда ( true ), а если 0 то ответом будет не правда ( false ). Думаю не буду разбирать пример с буленом так как тут и так все понятно.
Починить этот баг очень просто, заменив всего одну линию:
if(it != null && it != “”)
Ответ на этот раз будет:
flist:
Hello
2
false
End
0
Me?
true
100elist:
null
Если в этот пример хорошенько вдуматься то можно увидеть своего рода прикольную ошибку. Меня она лично порадовала, так как код будет работать правильно в большинстве случаев, но при этом код кривой. Жава а этот счет более адекватна, так как она не позволит сделать if(tmp) – так как она не динамична как груви, но и писать нужно будет больше кода. Вот пример примерно того же только на Жаве:
Vector list = new Vector();
Vector flist = new Vector();
Vector elist = new Vector();list.add(“Hello”);
list.add(null);
list.add(2);
list.add(false);
list.add(“End”);
list.add(“”);
list.add(0);
list.add(“Me?”);
list.add(true);
list.add(100);for (Iterator it = list.iterator(); it.hasNext();) {
Object o = it.next();
if(o instanceof java.lang.String) {
if(o != “”) flist.add(o);
else elist.add(o);
}
if(o instanceof java.lang.Integer) {
flist.add(o);
}
if(o == null) {
elist.add(o);
}
}
System.out.println(“flist:”);
for (Iterator it = flist.iterator(); it.hasNext();) {
System.out.println(it.next());
}
System.out.println(“\nelist:”);
for (Iterator it = elist.iterator(); it.hasNext();) {
System.out.println(it.next());
}
Кода больше, а по сути делает тоже самое. Хотя не совсем, этот код довольно лимитирован тем что может быть в листе, на данный момент только String, Integer и просто null. Но все же забавно!
Ладно у кого будут мысли пишите, а я спать.
Да уж, читая код на Java мне становится нехорошо, а Groovy прекрасен, по лаконичности смахивает на Ruby.