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

    @ComatoZZZ
    Нормальный код. Странно что он не понравился.
    GetConverter возвращает фабрику а не конвертор и лучше бы добавить Factory к названию
    Ответ написан
    1 комментарий
  • Код ревью или что не так с моим тестовым заданием?

    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().

    В общем как-то так.
    Ответ написан
    2 комментария
  • Зачем нужен Dependency Injection в Android разработке?

    artemgapchenko
    @artemgapchenko
    Начать неплохо бы с понимания того, что такое DI. Обратимся к википедии:

    Внедрение зависимости (англ. Dependency injection, DI) — процесс предоставления внешней зависимости программному компоненту.

    Если выражаться не канцеляритом, а обычным русским языком, то DI - это когда вы своему компоненту (например, классу) предоставляете нужные для него зависимости извне, а не создаете их сами в конструкторе, или через инициализацию в месте объявления поля. То есть не так:

    public class Api {
    	....
    	private final HttpClient client = new OkClient();
    }

    А, например, так:

    public class Api {
    	....
    	private final HttpClient client;
    
    	public Api(@NonNull HttpClient client) {
    		this.client = client;
    	}
    }


    И что нам это даёт?

    Ну, очевидно, нам теперь проще менять зависимости. Нужна вам другая реализация абстрактного класса HttpClient - взяли, и передали её через конструктор, или через метод-setter. В случае с первым куском кода, вам пришлось бы ещё и класс Api переписывать, что в случаях, отличных от тривиальных, может привести к ошибкам. Получается, что ваш класс теперь закрыт от изменений (смотрим Open/Closed Principle).

    Окей, а на практике-то какие бенефиты?

    Во-первых, вы теперь можете написать инициализацию вашей программы через конфигурационные файлы. Скажем, на старте будет читаться простенький текстовый файл, который определяет, какой httpclient использовать, какие настройки доступа к бд применять и так далее, и, исходя из этого, будут определяться зависимости.
    Во-вторых, вам теперь существенно проще писать тесты. Написали вы, скажем, какой-нибудь парсер, который принимает InputStream, содержащий в себе данные json-объекта, как-то хитро его парсит, и выдаёт вам объект вашей бизнес-модели. В приложении этот парсер будет принимать на вход реализацию InputStream'а, которая берёт данные из сети, а в тестах - реализацию, которая просто читает файл с диска (потому что тесты должны выполняться часто и быстро, и ваша задача в тесте - протестировать ваш парсер, а не скорость сетевого соединения).

    Вот, в общем-то, и всё. А Dagger - это просто библиотека, которая автоматизирует ручное внедрение зависимостей, равно как и другие DI-библиотеки.

    P.S. В некоторых случаях чрезмерное увлечение DI может привести к нежелательным эффектам, вроде чрезмерного усложнения кода, поэтому внедряйте аккуратно. Понимание приходит с опытом.
    Ответ написан
    Комментировать
  • Код ревью или что не так с моим тестовым заданием?

    @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 используется несколько раз. По мне удобней завести локальную переменную с адекватным значению названием.

    Почитайте "Совершенный Код" Макконнелла - шикарнейшая книга для разработчиков
    Ответ написан
    2 комментария
  • Код ревью или что не так с моим тестовым заданием?

    @dmitryKovalskiy
    программист средней руки
    Лично у меня код вида
    setupList();
    showListProgress(true);
    getTaskList();

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

    Поскольку сам работаю на шарпе, комментировать стиль написания не могу. Может какие-то приемы это классика
    Ответ написан
    Комментировать
  • Код ревью или что не так с моим тестовым заданием?

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

    saboteur_kiev
    @saboteur_kiev Куратор тега Карьера в IT
    software engineer
    Я вообще не программист, даже не java junior.

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

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