Код ревью или что не так с моим тестовым заданием?

Недавно проходил собеседование на позицию Middle Android Developer в одну из компаний по разработке приложений.
После разговоров с HR и технического собеседования с тимлидом, мне дали тестовое задание на дом. Срок выполнения 5 часов.
Успешно выполнив задание за 4,5 часа и отправив его HRу стал с нетерпением ждать результата.
На следующий день получил ответ "Уровень выполнения тестового задания оставляет желать лучшего."
Ответ меня мягко говоря ошарашил... Я написал письмо с просьбой подробнее описать мои ошибки. В ответ получил игнор...
Прошу желающих дать комментарии по исполнению этого тестового задания. Что же не так в исполнении?
Проект на GitHub https://github.com/krawa/EstafetaTest
Всем спасибо!
  • Вопрос задан
  • 2567 просмотров
Пригласить эксперта
Ответы на вопрос 6
@dmitryKovalskiy
программист средней руки
Лично у меня код вида
setupList();
showListProgress(true);
getTaskList();

ассоциируется с процедурным программированием. Java это насколько я знаю ООП. А тут ни паттернов, ничего. Покрыть тестами такой код я не знаю как. Наверняка внутри идет обращение к параметрам окружения или глобальным переменным, мокировать которые просто праздник.
Разделения логики на слои я тут не вижу и внутри методов есть и получение данных и настройки отображения, однако на мой взгляд это проблема не сколько программиста, сколько человека, поставившего задачу за 5 часов написать целое приложение. На мой взгляд лучше бы или дал сутки или дал конкретные задачи на алгоритмы.

Поскольку сам работаю на шарпе, комментировать стиль написания не могу. Может какие-то приемы это классика
Ответ написан
Комментировать
@heahoh
Full stackoverflow developer
Мнение относительно оформления, так как скилзами в java не обладаю, - код, конечно, пишется для машины, и ей без разницы, как он написан - лишь бы работал, но читать человеку и разбираться для сопровождения и расширения функционала - человеку.

adapter/TaskListAdapter.java
public boolean areItemsTheSame(Task item1, Task item2) {
                return !(item1 == null || item2 == null) && item1.getId() == item2.getId();
            }

Если не (item1 или item2 равны null) и id итемов одинаковы...
Как я понимаю, если в метод передать item1 = null, то у вас будет nullPointerException или что-то подобное?
Почему бы не изменить конструкцию на item1 != null && item2 != null && item1.getId() == item2.getId(), мне кажется будет читабельней. Возможно покажется глупым использовать длинные названия переменных, вроде originalTask и taskToCompare, но они явно лучше, чем item1 и item2, да и автокомплит IDE поможет в наборе названий

Тернарный оператор внутри if пугает также, как и if без открытия скобок
model/Task.java
if (ActualEndDate != null ? !ActualEndDate.equals(task.ActualEndDate) : task.ActualEndDate != null)
            return false;

Поясню - замыленный глаз, конец рабочего дня и голова совсем не варит. Нужно срочно закрыть таску - открываем код и видим необходимый участок, который собираемся закомментить
...
if (длинное условие)
            doSomething();
doAnotherThing();
...


...
if (длинное условие)
//            doSomething();
doAnotherThing();
...

и теперь наш doAnotherThink выполняется не всегда, а лишь при выполнении условия.
Вот пример такой ошибки в реальном проекте (пункт "Неправильно закомментированная строка")

Ну и
result = 31 * result;
Где 31 используется несколько раз. По мне удобней завести локальную переменную с адекватным значению названием.

Почитайте "Совершенный Код" Макконнелла - шикарнейшая книга для разработчиков
Ответ написан
saboteur_kiev
@saboteur_kiev Куратор тега Карьера в IT
software engineer
Я вообще не программист, даже не java junior.

Но например быстрый взгляд на комментарии говорит, что вы никогда не пользовались javadocs, а для мидера это скорее всего непростительно.

Названия переменных у вас вроде бы имеют стиль, но не имеют смысла. Смотря на код, непонятно что именно хранится в какой-то переменной ну вообще.
Ответ написан
Комментировать
mitaichik
@mitaichik
Сам в андройде не профессионал, скорее любитель, но свои 5 копеек вставлю:

private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = this;
    }


Хз почему так, но это считается не камильфо. Сам так юзаю, и не одного краш-дампа не словил, но в android-сообществе это не приветствуется (надеюсь в комментах кто-то опишет почему). Принято передавать контекст.

В MainActivity методы для работ с меню можно было удалить, ибо, судя по всему они нигде не юзаются (и видимо остались от типового шаблона).

В RestClient идет присвоение в статическю переменную.

private static RestClient instance;

Я так понимаю ты делал синглтон. Хз приемлемо ли это в андройде, но я б сделал его как компонент приложения. В идеале вся эта фигня должна создаваться через DI (по карйней мере в бэкенд-разработке DI везде используется). Для андройда это библиотека Dagger 2 (сам еще не юзал, но выглядит многообещающе).

Плюс там же:

public static RestAPI get() {
        if(instance == null) instance = new RestClient();
        return instance.restAPI;
    }


Этот метод по хорошему должен быть помечен как synchronized: если туда зайдут одновременно 2 потока, то может получиться что создадутся 2 RestClient'a. Тут конечно побоку - это вряд-ли что то сломает, но в крупных приложениях это ого-го какие проблемы создаст (я опять таки про бэкенд, ибо я больше по нему).

Хранить во так параметры аутентификации - тоже не есть хорошо, но это тестовое задание, так что побоку.

Теперь по фрагментам:

list = (RecyclerView) v.findViewById(R.id.list);

Ты присваиваешь свойствам фрагмента вьюхи, в onDestroyView не плохо бы их обнулить. А еще лучше, юзать butterknife.

Вызов запросов в фрагменте нынче не моден. По хорошему бы сделать какую-нить службу-обертку над retrofit (службу не в смысле андройд службу, а в смысле паттерн), которой ты говоришь загрузи-ка мне данные, и ловишь потом от нее сообщения (в этом тебе поможет otto от square). При показе фрагмента ты запрашиваешь данные и подписываешся на события от сервиса, при скрытии фрагмента - отписываешся.

В обработке ответов ты проверяешь if (getActivity() == null) return;. Но этим ты проверяешь наличие активити, но не фрагмента, фрагмент может быть уничтожен, или его вьюха может быть уничтожена, или у активити может быть вообще другой фрагмент или еще что. Короче, это все приведет к крэшу.

onTaskClick: По хорошему, менять фрагменты должна активити, фрагмент списка просто должен сказать "выбран такая-то задача", и вызвать каллбэк активити. А уже активити принять решение что делать дальше. В официальной документации описано как это сделать (механизм каллбэков).

Реально смутил DetailsTaskFragment: для представления информации о одной задаче ты юзаешь RecyclerView. Имхо, это в корне не правильно. Я понимаю твою мотивацию, почему ты так сделал (экономия память на вьюхах и прочее). Но ты жестко задал структуру и тип отображения. Малейшее изменение требования отображения задачи, и весь этот код под удаление. Ну и инструмент неправильный: RecyclerView - это для списков, для больших списков, а задача - это не список, это сущьность. Если бы ты отказался от этой идеи, и заюзал обычный layout + DataBinding все было бы более удобно, изменяемо, и кода было бы раз в 10 меньше.

В TaskListAdapter у тебя примешана бизнес-логика, а именно сортировка. По хорошему ты должен делать сортировку в другом месте, например, в службе которая тебе отдает данные, или где то еще, но точно не в UI, которым является адаптер.

ItemViewHolder - там у тебя обработчик. Хз правильно это или нет, но в примерх гугла обработчик вешается в onBindViewHolder.

Так же не совсем понимаю (возможно просто задание не читал) метод addAll: почему бы просто не обновить список? Плюс опять это по большой части бизнес-логика. И что странно - не вижу notifyDataSetChanged().

В общем как-то так.
Ответ написан
@Tiberal
Все в кучу. Бизнес логика перемешана с отображением, мидл должен иметь представление как все это разделить. Можно было шаблонизировать те же адаптеры и фрагменты. Мож еще что, особо не всматривался=)
Ответ написан
Комментировать
@ComatoZZZ
Нормальный код. Странно что он не понравился.
GetConverter возвращает фабрику а не конвертор и лучше бы добавить Factory к названию
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы