Случайны выбор дневника Раскрыть/свернуть полный список возможностей
\n";
}


Начало таблицы формируется в функции onCreatе, а конец уже в классе ResultTest, который выводит результат прохождения теста.

После того как пользователь дошел до последнего слова или завершил тест, открывается активтити, где выведен результат, как отмечалось выше это класс ResultTest, я не думаю, что он требует дополнительных комментариев, но следует заметить, что для построения Html-таблицы была использована функция loadDataWithBaseURL(null, TestWords.table_result, «text/html», «UTF-8», null), а также применен WebView.



Вот и подобрался я к самому интересному, а именно к загрузке слов с сервера. Класс отвечающий за это называется Load, на его реализацию ушло примерно 18 часов, т.к. там используется класс RecyclerView, а это для меня было что-то новое и в начале он вел меня в ступор, но в итоге, я все же с ним разобрался. Так что будет правильным, если свое повествование, о создании класса Load, я начну именно с него.



Первым делом хочется сказать, что этот класс получился достаточно большим, как ни старался его уменьшить, но все же рабочим. Для его использования был создан xml-файл с CheckBox'ом и TextView'ом, изначально скрытым. Здесь TextView служит для создания алфавита указателя, как можно увидеть на картинке выше. О том как он создается, я уже говорил, но здесь есть один момент, без которого, создание алфавита не получится и его опишу чуть позже.



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



Сначала я перегрузил три функции, onCreateViewHolder, onBindViewHolder, getItemCount, а также класс ViewHolder. GetItemCount не представляет особого интереса, т.к. она возвращает только размерность. Начну свое описание с класса ViewHolder.



public static class ViewHolder extends RecyclerView.ViewHolder {
public CheckBox chkbox;
public TextView tv_alph;
public ViewHolder(View v) {
super(v);
chkbox = (CheckBox) v.findViewById(R.id.rv_chkbox);
tv_alph = (TextView) v.findViewById(R.id.tv_alph);
}
}


Этот класс довольно прост и не требует никаких комментариев. Далее идет функция onCreateViewHolder, она служит для создания объектов ViewHolder.



public RecyclerLoad.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.content_checkbox, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}


Теперь рассмотрим функцию onBindViewHolder.



public void onBindViewHolder(final ViewHolder holder, final int position) {
if(index_alph[position]) {
holder.tv_alph.setText(Alph[position]);
holder.tv_alph.setVisibility(View.VISIBLE);
} else holder.tv_alph.setVisibility(View.GONE);
holder.chkbox.setText(Eng_Array[position] + " - [" +
Trans_Array[position] + "] - " +
Rus_Array[position]);
if(this_load.menu_load_1 != null)
this_load.menu_load_1.setTitle("Загрузить все слова (" + Integer.toString(amount) + ")");
holder.chkbox.setOnCheckedChangeListener(null);
holder.chkbox.setChecked(checked_box[position]);
holder.chkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked)
checked_words_load++;
else
checked_words_load--;
checked_box[position] = isChecked;
if(this_load.menu_load_2 != null)
this_load.menu_load_2.setTitle("Заг. выделенные слова (" + Integer.toString(checked_words_load) + ")");
}
});
if (position >= amount - 1) {
Log.d("MyLog", "Подкачка!!!");
LoadWords(sub_str);
onBind = true;
}
}


Что интересного здесь можно отметить, первое — объект holder заполняется данными, второе — чтобы не было дублирования нажатия CheckBox'ов вызываются функции setOnCheckedChangeListener и setChecked, далее во всех CheckBox'ах перегружается функция onCheckedChanged, для того чтобы в пункте меню менялось значение «количетсво» и последнее — это оператора if проверяющий условие предпоследнего слова, как только пользователь до него доходит вызывается функция подгрузки слов LoadWords.



Внимательный читатель наверное неоднократно заметил переменную sub_str, настало время рассказать о ней подробнее. Это переменная String, он хранит строку введенную пользователем в Edit'е «Найти: ». Т.к. в программе реализован живой поиск, то она передаются в функцию после каждого изменения текста в данном Edit'e, код представлен ниже.



find_words.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
FindWord(null);
}
});
public void FindWord(View view) {
mAdapter.Find(find_words.getText().toString());
mAdapter.notifyDataSetChanged();
}


Из кода видно, что после изменения текста вызывается функция FindWord, в которой вызывается метод Find, класса RecyclerLoad, где загружаются слова с сервера, которые содержат подстроку введенную пользователем, а затем вызывается функция обновления.



public void Find(String sub_str) {
this.sub_str = sub_str;
amount = 0;
Eng_Array = null;
Trans_Array = null;
Rus_Array = null;
checked_box = null;
index_alph = null;
Alph = null;
LoadWords(sub_str);
}


Перед вызовом LoadWords, все переменные класса RecyclerLoad обновляются, т.к. загружаются совершенно новые данные.



public void LoadWords(String sub_str) {
ClientLoadWords clw = new ClientLoadWords();
clw.LoadWords("2(!!)" + (sub_str.length() != 0 ? sub_str : "(--)"), amount);
try {
clw.join();
} catch (InterruptedException e) {
Log.d("MyLog", "ShowViewWords: " + e);
}
if(clw.int_error == -1) {
Toast toast = Toast.makeText(this_load.getApplicationContext(),
"Нет соединения с сервером!", Toast.LENGTH_SHORT);
toast.show();
}
amount += clw.amount;
Log.d("MyLog", "amount = " + amount);
String [] temp_Eng_Array = new String[amount];
String [] temp_Trans_Array = new String[amount];
String [] temp_Rus_Array = new String[amount];
String [] temp_Alph = new String[amount];
boolean [] temp_checked_box = new boolean[amount];
boolean [] temp_index_alph = new boolean[amount];
int temp_amount = Eng_Array != null ? Eng_Array.length : 0;
if(Eng_Array != null)
for(int i = 0; i < Eng_Array.length; i++) {
temp_Eng_Array[i] = Eng_Array[i];
temp_Trans_Array[i] = Trans_Array[i];
temp_Rus_Array[i] = Rus_Array[i];
temp_Alph[i] = Alph[i];
temp_checked_box[i] = checked_box[i];
temp_index_alph[i] = index_alph[i];
}
for(int i = 0; i < clw.amount; i++) {
temp_Eng_Array[i + temp_amount] = clw.Eng_Array[i];
temp_Trans_Array[i + temp_amount] = clw.Trans_Array[i];
temp_Rus_Array[i + temp_amount] = clw.Rus_Array[i];
temp_checked_box[i + temp_amount] = false;
temp_index_alph[i + temp_amount] = false;
temp_Alph[i + temp_amount] = "";
if(!first_chr.equals(String.valueOf(clw.Eng_Array[i].toUpperCase().charAt(0)))) {
Log.d("MyLog", "First Chr: " + first_chr + ", me.getKey(): " + clw.Eng_Array[i].toUpperCase().charAt(0) +
" boolean: " + first_chr.equals(String.valueOf(clw.Eng_Array[i].toUpperCase().charAt(0))));
first_chr = String.valueOf(clw.Eng_Array[i].toUpperCase().charAt(0));
temp_Alph[i + temp_amount] = first_chr + ":";
temp_index_alph[i + temp_amount] = true;
}
}
for(int i = 0; i < temp_Eng_Array.length; i++) {
Log.d("MyLog", "temp_Alph: " + temp_Alph[i] + ", temp_index_alph = " + temp_index_alph[i] + ", i = " + i);
}
Eng_Array = temp_Eng_Array;
Trans_Array = temp_Trans_Array;
Rus_Array = temp_Rus_Array;
checked_box = temp_checked_box;
index_alph = temp_index_alph;
Alph = temp_Alph;
}


Функция LoadWords получилась большой, так что я отмечу самые важные моменты. Во-первых класс ClientLoadWords — это подобие класса ClientAddWords, только он принимает данные с сервера, а не отправляет. Ему так же передается строка с кодом операции и строкой поиска. Второй параметр функции LoadWords, количество слов уже полученных слов, эта переменная нужна, чтобы сервер знал сколько он слов отправил и сколько осталось отправить еще. Код функции LoadWords приведен ниже.



public void run() {
InetAddress addr = null;
try {
addr = InetAddress.getByName("192.168.1.137");
} catch (UnknownHostException e) {
Log.d("MyLog", "ClientLoadWords LoadWords 1: " + e);
}
Client c;
try {
c = new Client(addr);
} catch (IOException e) {
Log.d("MyLog", "Socket failed: " + e);
int_error = -1;
return;
}
c.Out(str_user);
c.Out(Integer.toString(begin));
amount = 0;
try {
amount = Integer.parseInt(c.In());
} catch (IOException e) {
Log.d("MyLog", "LoadWords ClientLoadWords 3: " + e);
}
Log.d("MyLog", "Amount: " + amount);
Id_Array = new int[amount];
Eng_Array = new String[amount];
Trans_Array = new String[amount];
Rus_Array = new String[amount];
try {
for (int i = 0; i < amount; i++) {
Id_Array[i] = Integer.parseInt(c.In());
Eng_Array[i] = c.In();
Trans_Array[i] = c.In();
Rus_Array[i] = c.In();
}
} catch (IOException e) {
Log.d("MyLog", "LoadWords ClientLoadWords 4: " + e);
}
Sort();
}


Самое интересное в этой функции, это получение данных. С начала, клиентское приложение получает количество отправляемых слов, затем создаются массивы и потом уже в цикле for, загружаются все слова, а далее, как ни трудно догадаться они сортируются.



Теперь вернемся в функцию LoadWords. Конструкция clw.join() ожидает завершения потока в классе ClientLoadWords, и только затем продолжает выполнение остальных операторов. Так же если объект clw возвращает код ошибка равный -1, то выводится подсказка и функция завершает свое выполнение. Далее, либо инициализируются массивы и туда заносятся полученные данные, либо уже к этим массивам данные дополняются, а также алфавитный указатель сразу жестко закрепляется и булевы значени отображения TextView'а заносятся в массив index_alph, иначе бы TextView дублировался.



Я думаю, на этом рассмотрение клиентского приложения можно закончить, т.к. самые интересные моменты, такие как отправка данных, живой поиск, создание теста, подгрузка слов и т. д., были рассмотрены, а пункт «Просмотреть слова», можно и пропустить, потому что его создания практически ничем не отличается от вывода результат после окончания теста, по сути это та же Html-таблица, только не цветная, так что приступим к серверному приложению.



Серверное приложение



Серверное приложение, является многопоточным, использует базу данных MySQL и передает данные по средством протокола TCP, это все что нужно знать, чтобы приступить к рассмотрению программа. За принятие/отправку данных отвечает класс ServerOneJabber. Он наследует класс Thread. Полностью приводить код данного класса я не буду, т.к. это не имеет смысла.



public ServerOneJabber(Socket s) throws IOException, SQLException {
socket = s;

in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

sqldb = new SqlDB();

start();
}


Конструктор не представляет ничего сложного, стоит только обратить внимание на объект sqldb, все что о нем следует знать, это то, что он отвечает за связь с БД. Объекты in и out далее оборачиваются в функции In и Out, это сделано для удобства.



public void run() {
String message_user = new String("");

try {
message_user = Out();
} catch(IOException e) {
System.out.println(e);
}

System.out.println("message_user: " + message_user);

try {
switch(message_user.charAt(0)) {
case '1':
AddWord(message_user);
break;
case '2':
TransferWord(message_user);
break;
}
} catch(SQLException e) {
System.out.println(e);
} catch(IOException e) {
System.out.println(e);
}
}


Предназначение функции run заключается только в определении кода операции и вызова соответствующей функции. Рассмотрим каждую по очереди.



public void AddWord(String str) throws SQLException  {
String[] str_user = Split(str, "(!!)");

for(int i = 0; i < str_user.length; i++)
System.out.println("str_user: " + str_user[i]);

sqldb.AddWord(str_user[1], str_user[2], str_user[3]);
}


В функции AddWord все понятно. В sqldb передаются данные карточки и заносятся в БД с помощью метода AddWord. Метод Split получает на вход строку отправленную клиентом и строку разделитель, она была написана, потому что стандартная функция split класса String работала не правильно, я так и не разобрался почему.



public void TransferWord(String str) throws SQLException, IOException {
String [] user_str = Split(str, "(!!)");

for(int i = 0; i < user_str.length; i++) {
System.out.println("user_str: " + user_str[i]);
}

if(sqldb.CountSQL() == 0) {
In("0");
return;
}

int begin = new Integer(Out());

System.out.println("Loading Words...");

String [] data;

if(user_str[1].equals("(--)"))
data = sqldb.AllWords();
else
data = sqldb.Find(user_str[1]);

read_amount_words = (data.length - begin > 100 ? 100 : data.length - begin);

In(Integer.toString(read_amount_words));

for(int i = begin; i < begin + ((data.length - begin) > 100 ? 100 : data.length); i++) {
System.out.println("i = " + i + ", data.length = " + data.length);

if(i == data.length)
break;

String [] str_data = Split(data[i], "(!!)");

for(int j = 0; j < str_data.length; j++)
System.out.println("str_data: " + str_data[j]);

In(str_data[0]);
In(str_data[1]);
In(str_data[2]);
In(str_data[3]);
}
}


Функция TransferWord передает данные клиенту. Первое, что делает функция, это проверяет строку поиска, если пользователь не пользовался поиском, то функция возвращает все слова, иначе только те, которые содержат данную подстроку. После этого проверяется сколько слов было передано и сколько осталось, если в БД осталось больше 100 слов, то передаются следующее 100 слов с отметки, на которой закончилась предыдущая передача, иначе передается остаток.



public class Server {
static final int PORT = 8080;

static public void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(PORT);
System.out.println("Server Started");

try {
while(true) {
Socket socket = s.accept();
try {
System.out.println("Client Connection");
new ServerOneJabber(socket);
} catch(IOException e) {
socket.close();
}
}
} catch(SQLException e) {
System.out.println(e);
} finally {
s.close();
}
}
}


В классе Server содержится метод main, и если к серверному приложению подключается клиент, то создается класс ServerOneJabber в новом потоке.



Заключение



Вот и подошла к завершению данная статья. Теперь, после написания данной программы, я могу учить английские слова, когда сижу на парах, где-нибудь в очереди или же дома. Надеюсь это статья окажется для кого-нибудь полезной, старался описать все как можно подробнее, но это у меня всегда плохо получалось. Спасибо за внимание и потраченное время.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303064/



Найдено 16926 сообщений
Cообщения с меткой

java - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

[Перевод] Язык Go, микросервисы и DevOps – хорошая компания?

Пятница, 24 Июня 2016 г. 15:18 (ссылка)

Привет, Хабр!



Напоминаем, что все желающие по-прежнему могут приобрести отличную книгу Сэма Ньюмена "Создание микросервисов". Поскольку наши ожидания эта тема более чем оправдала, мы продолжаем искать связанную с ней литературу и не так давно обратили внимание на книгу о программировании микросервисов на языке Go







Интересную статью с обоснованием этого подхода мы нашли в блоге Agile Maverick, и ее перевод размещаем под катом.



Приятного чтения!







Сейчас все говорят о микросервисах и DevOps. Поставьте эти словечки себе в профиль – и вас сразу начнут осаждать рекрутеры. Я побывал в Мюнхене на нескольких интересных митапах по микросервисам, и меня наиболее удивило, что эта тема пользуется наибольшим интересом в сообществах Java и Scala. Удивило потому, что Java и Scala – очень насыщенные языки, в которых есть из чего выбирать.



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



Я понимаю микросервисную архитектуру в более целостном ключе. Я бы сказал, что и вся экосистема должна быть проще и компактнее. Реализация – лишь одна сторона медали. Другая сторона – это время исполнения и сопутствующие фреймворки. Здесь мы подходим к теме DevOps – философии, стремящейся увязать друг с другом две эти стороны.



Виртуальная машина Java оптимизирована для работы с долгоиграющими приложениями, в ней действует одна из наиболее выверенных и затейливых систем сборки мусора. Используется в боевых условиях уже более 10 лет. Тем не менее, когда мне доводится видеть современные высокодоступные архитектуры – сразу напрашивается вопрос: а нужны ли долгоиграющие приложения для реализации абсолютного большинства существующих сервисов?



Приведу пример. Я участвовал в разработке приложения для кодировки видео, и это приложение как назло должно было работать круглосуточно с минимальными задержками. Мы думали, остановиться ли на стабильном языке программирования вроде Java или написать приложение на Go, где использовались бы имеющиеся библиотеки на C для кодирования и декодирования, однако такой проект мог обернуться утечками в памяти. Наконец, мы решили разделить приложение на различные процессы; статический бэкенд почти не изменился, поскольку передавал информацию по практически не изменившемуся протоколу, а еще у нас была функционально богатая клиентская часть, где существовал риск утечек. Обе части использовали разделяемую память. Оказалось, что вариант хороший. Поскольку Go стартует быстро, мы перезапускали клиентскую часть раз в десять секунд. Оказалось, что проблема – не в утечках памяти, а в оперативных обновлениях.



За много лет в Java сложилось много нетривиальных решений – например, фреймворк log4j для логирования. На примере контейнерных решений вроде OpenShift можно убедиться, что теперь снова принято работать с stdout и stderr. Нет необходимости внедрять изощренные решения для логирования на уровне языка. Этот пример позволяет судить, как DevOps и новые среды времени выполнения меняют правила игры.



Типичный docker-образ на Go docker имеет размер около 15 MB; сравните его с образом для JVM на Java, размер которого — около 300 MB. Разница 1 к 10. Java JVM оптимизирована под экономный расход памяти, но все равно требует примерно в 10 раз больше памяти, чем Go.



В Go не так много унаследованных фреймворков, поэтому и зависимостей обычно мало, а код зависимостей входит в состав бинарного файла. Поэтому отпадает необходимость в таких сложных инструментах как Maven. В контейнерной среде релиз нового образа необходим всякий раз, когда меняется одна из зависимостей в цепочке. А значит, на Java Java мы должны обновлять такие контейнеры достаточно часто. Хуже того, зависимости обычно запрятаны где-то глубоко.



Java и Scala – это языки для объектно-ориентированного программирования. Но при работе в сравнительно простых предметных областях такие решения кажутся мне довольно затратными. «Гибкий» аспект философии Go позволяет организовать разработку не только не хуже, но и гораздо понятнее.



Java известен своим огромным конвейером и множеством инструментов для непрерывной интеграции вроде Jenkins, которые развились вокруг этого конвейера. В Go конвейеры получаются гораздо короче, проще и быстрее – ведь мы получаем бинарные файлы, уже готовые к исполнению.



В 1990-е был настоящий бум серверов приложений Java – считалось, что они обеспечат независимость разработки от операционной системы и аппаратного обеспечения. Читая спецификацию JEE, мы также рассчитывали на простоту удаленных взаимодействий и компонент-ориентированную разработку. Когда я вижу контейнер docker, на котором работают Java-приложения, всегда вспоминаю о новой версии EJB. В принципе, стек Java не упростился, но теперь он упакован в контейнер. Такая упаковка даром не дается, поскольку добавляется еще один уровень сложности; вы с ним познакомитесь, как только попробуете отладить сеть такого docker-контейнера.



Go docker – вариант для масштабирования сервисов, но сложную среду времени исполнения он не спасает. Если у вас всего один простой сервис, то простые бинарные файлы Go можно выполнять прямо на хосте. Если же речь идет о более сложном приложении, то сервисы можно положить, например, в контейнер и запускать их в PaaS-среде вроде OpenShift. Чтобы протестировать сервис на ноутбуке разработчика, контейнер не нужен, всяческая связанная с ним магия – тоже.



Go прост, и обучаешься ему достаточно быстро. Основные концепции Go усваиваются всего за неделю-две. Поэтому новые коллеги могут с успехом изучить реализации сервисов, а разработчики или операторы могут быстрее фиксить баги, не отвлекаясь при этом от основной работы, даже если не они реализовывали эти сервисы. Автономные команды – дело хорошее, но если у вас всего трое коллег, то обеспечить круглосуточную доступность становится реально сложно.



Go, где вариантов не так много, помогает быстрее освоиться с решением тем разработчикам, которые сами его не писали. Не требуется углубляться в философию разработки. Разумеется, всегда можно реализовывать сервисы и в более простом стиле на более насыщенных языках, например, на Java или Scala, но в данном случае нужно научиться самоограничению, обсуждать все детали с командой – соответственно, микросервисная архитектура обрастает огромной документацией.



Мне кажется, Go идеально подходит для реализации микросервисов. Почему же этот язык так медленно усваивается в сообществе разработчиков?



Думаю, просто никто не любит резких изменений. Мы попробовали изменить всего одно измерение в многомерном мире программирования. Это измерение – размер сервиса. По моему опыту, изменить одно измерение еще недостаточно, чтобы пошла эволюция. Поскольку все измерения взаимосвязаны, они влияют друг на друга. Решив упростить приложение, переделав его в виде микросервисов, мы должны также упростить и исполняющую среду, и язык программирования, с которым работаем. Иначе получим лишь новую головную боль – например, придется управлять сразу множеством JVM, которые занимают кучу места в памяти и запускаются довольно медленно. Либо получим множество мелких объектно-ориентированных решений, которые будут распределенными, а значит – более сложными. Наконец, мы просто запутаемся со множеством сервисов, образующих огромное дерево зависимостей.



По-моему, не меняя всех измерений сразу, мы словно пересаживаемся с лошади на машину, но берем с собой седло и шпоры.



Давайте не просто писать микросервисы, но и адаптировать под них всю архитектуру. Go отлично для этого подходит. Если предметная область сервиса расширится, вы всегда сможете дополнительно воспользоваться Java или Scala.




Актуальность книги






































Проголосовало 24 человека. Воздержалось 9 человек.





Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.


Original source: habrahabr.ru.

https://habrahabr.ru/post/304040/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Как стать профессиональным веб-разработчиком: практическое руководство

Среда, 23 Июня 2016 г. 01:46 (ссылка)



Дорога длинна и трудна, но интересна и полезна!



Статья задумывалась как практическое руководство для желающих стать профессиональным веб-разработчиком. Я уже более 20 лет пишу код для веба. Я ежедневно работаю с веб-разработчиками и помогаю им. В статье я опишу, что вам нужно выучить, когда вам нужно это выучить и где взять информацию (чаще всего даже бесплатно). Затем я дам совет по получению реального опыта, и что самое важное – по получению денег за написание кода.



По статье разбросано множество ссылок на бесплатные и важные ресурсы. Для простоты я собрал их в PDF и разбил по категориям. Мне не платят за упоминания сайтов, ссылки на которые я привёл – я всего лишь хочу порекомендовать вам лучшие ресурсы, чтобы помочь вам достичь вашей цели.



Что нужно помнить:



1. Статью разрешается пролистывать



Руководство может помочь вам вне зависимости от вашего положения на дороге к профессиональной разработке. Прокрутите его к тому заголовку, который лучше всего описывает ваше сегодняшнее положение, и читайте оттуда. Если вы только начали этот путь, или пока размышляете об этом – последуйте совету Короля из «Алисы в стране чудес»:



Начните с начала, и продолжайте, пока не дойдёте до конца; и там уже остановитесь.



2. Попробуйте всего понемногу, а затем выбирайте специализацию.



Деньги – не самое важное. Вам необходимо ЛЮБИТЬ ваше занятие! Но вы не узнаете, что вам нравится, пока не попробуете.





Найдите свою страсть, а потом монетизируйте её



Руководство поможет вам ознакомиться с как можно большим количеством областей веб-разработки как можно быстрее. А потом поможет вам выбрать специализацию в той области, что вам понравилась. Сначала вы не достигнете совершенства ни в чём – вы выучите основы, и потом продолжите изучение. Найдите свою страсть, и я покажу вам, как двигаться дальше.



Я решил писать код. Мне нравится веб. Я не знаю, с чего начать





У вас всё получится!



Поздравляю! Это серьёзный первый шаг и начало чего-то весьма захватывающего. Но это может оказаться и очень сложным. Не волнуйтесь, я помогу.



Сначала вам нужно быстро познакомиться с основами всех областей веб-разработки («полный цикл»). Обучение будет разнообразным, но неглубоким. Это нужно для того, чтобы найти область, которая вам нравится, а также приобрести основные навыки в разных областях. Тогда вы сможете понимать и работать с большим набором задач, вне зависимости от того, на чём вы остановите свой выбор.



Изучите основы HTML



Язык разметки гипертекста, Hypertext Markup Language (HTML), контролирует содержимое и разметку того, что вы видите в браузере. Начав с него, вы получаете интерфейс пользователя, с которым можно взаимодействовать, и видите результаты работы своего кода. При изучении более сложных языков его важность будет возрастать. Вам ведь не нужно кодить вслепую.



Вот, что вам нужно изучить на тему HTML:





Я уже знаю основы HTML



Круто! Это очень важный шаг. Теперь изучите основы JavaScript.



Изучите основы JavaScript



JavaScript – язык веба, и все основные браузеры (Chrome, Firefox, Safari, IE, множество других) поддерживают его. Каждый сайт, каждое веб-приложение, которым вы пользовались, скорее всего, содержит огромное количество JS-кода. Не говоря уже о том, что язык набирает популяность и на других платформах – сервера, настольные компьютеры, другие устройства.



Пока вам нужны основы, и для этого подойдут следующие ресурсы:





Я знаю основы JavaScript и HTML



Потрясающе! Теперь добавим к вашим навыкам CSS



Изучите CSS



CSS, или Cascading Style Sheets (каскадные таблицы стилей). Используются для настройки внешнего вида элементов HTML на странице. Ознакомьтесь с бесплатным обучающим материалом от Mozilla, а затем обращайтесь к ресурсу CSS-Tricks для решения самых сложных проблем (справа вверху есть поиск).



Переходим к бэкенду



До сих пор мы с вами рассматривали то, что называют «фронтендом» веб-разработки. Вы ознакомились с основными языками, работающими в браузере. Пора переходить к бэкенду – коду, работающему на сервере. Не волнуйтесь, сервер вам не понадобится – ваш компьютер справится с этим.



Языков для бэкенда масса, но поскольку вы знакомы с JavaScript, я порекомендую изучить использование Node.js. Он позволяет запускать JS-код на сервере, а не в браузере.



В дополнение к этому вам необходимо изучить Express и MongoDB.



Express


Это библиотека, с помощью которой Node.JS может работать веб-сервером (слушать запросы от страниц и отправлять им ответы).



MongoDB


Это база данных, позволяющая вам хранить и извлекать информацию.



Изучить три эти темы можно благодаря следующему прекрасному и бесплатному обучающему материалу, и его продолжению.



Мне нужно выбрать между «фронтенд», «бэкенд» и разработкой полного цикла



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









К этому моменту вы писали два типа кода. Один предназначен для взаимодействия с пользователем, другой – с данными. Что вы предпочитаете?



Взаимодействие с пользователем? Поздравляю, вы фронтенд-разработчик!



Взаимодействие с данными? Поздравляю, вы бэкенд-разработчик!



Оба? Поздравляю, вы разработчик полного цикла!



Ничего не понравилось? Поздравляю, веб-разработка – это не для вас. Возрадуйтесь, что вы поняли это сейчас, и не потеряли кучу времени и денег. Не готовы сдаваться? Может, вам не попался язык, который пришёлся бы вам по душе? Попробуйте изучить другие языки в разделе «Я хочу быть бэкенд-разработчиком».



Я хочу быть разработчиком полного цикла



Круто. Вам нужно ознакомиться со всем содержимым разделов «Я хочу быть бэкенд-разработчиком» и «Я хочу быть фронтенд-разработчиком».



Я хочу быть фронтенд-разработчиком и я знаю основы JavaScript, HTML и CSS



Для эффективной работы фронтенд-разработчиком вам необходимо в совершенстве овладеть HTML, CSS и клиентским JavaScript. Также вам нужно будет хорошо разобраться в нескольких важных фреймворках. Вы приобретёте навыки, которые работодатели и клиенты ожидают найти в разработчиках фронтенда.



К этому моменту вы уже должны знать основы HTML. В противном случае вернитесь к разделу «Изучите основы HTML».



Изучите промежуточный и продвинутый HTML


Ознакомьтесь с обучающим материалом по промежуточному HTML, а затем – по продвинутому.



Изучите продвинутый клиентский JavaScript




Отличная серия книг по JS, при этом бесплатная



Для поднятия вашего уровня владения JavaScript, я рекомендую серию книг «You Don’t Know JS» за авторством Кайла Симпсона. Автор выложил всю серию в онлайн совершенно бесплатно:





Кроме того, вашим лучшим другом должен стать и MDN JavaScript.



[Также совершенно бесплатно вам доступен превосходный перевод отличной книги "Выразительный JavaScript" — прим.перев.]



Знать «троицу фронтенда», HTML, CSS и JavaScript – это, конечно, здорово. Но для зарабатывания денег придётся вам познакомиться с некоторыми фреймворками.



Изучите jQuery


Это самая популярная библиотека JS всех времён. Хотя из-за некоторых новых фреймворков важность jQuery чуть поуменьшилась, если вы ищете работу, велика вероятность, что jQuery будет присутствовать в описании необходимых навыков (и упоминаться на собеседовании) ещё много лет.



Рекомендую изучить jQuery при помощи материалов FreeCodeCamp – это быстрый и эффективный метод. После этого отправляйтесь на официальный обучающий сайт – там вы найдёте дополнительные инструкции.



Также вам понадобится держать под рукой документацию по jQuery API.



Изучите популярный JS-фреймворк


Фреймворки делают работу с определённым языком или технологией легче, поскольку решают наибольшие проблемы, существующие у выбранной технологии. JavaScript очень благотворно повлиял на разработку и популяризацию фреймворков.



Чуть ли не раз в неделю появляется новый фреймворк, который прочат на место нового стандарта разработки. Вам нужно будет прошерстить сайты с вакансиями и немало погуглить, чтобы выяснить, какой фреймворк популярен на вашем рынке. Также рекомендую инструмент от Hacker News по оценке тенденций рынка вакансий.



Во время написания этой статьи следующие фреймворки пользовались популярностью:







React JS


React создали разработчики Facebook для работы с их архитектурой Flux. Это JS-библиотека для создания интерфейсов. Не так давно она вырвалась на первое место по популярности, перегнав Angular. Начинайте с React. Здесь можно найти хороший бесплатный курс для начинающих.



Angular 1 и 2


Angular JS создали разработчики Google, и он быстро набрал популярность. Многие компании сильно вложились в него, и, судя по графику выше, он всё ещё популярен. К сожалению, в Google приняли решение полностью переписать Angular при разработке 2-й версии. Поэтому Angular 1 и Angular 2 получились практически полностью разными. Если вам хочется стать экспертом в Angular, придётся изучить оба фреймворка. Возможно даже, что вам окажется достаточно и первой версии – пока ещё есть время. Но время это уже на исходе. Большинство работы, связанной с Angular, постепенно переходит на Angular 2. В Code School есть интересный бесплатный курс по Angular 1. А для изучения Angular 2 посмотрите бесплатные видео.



Ember JS


Для людей с опытом работы в Ember JS пока ещё есть места, но судя по графику, он уже помирает. Его не поддерживают такие монстры, как Google или Facebook, а вы и так будете загружены изучением React и Angular. Но если вам интересно, можете почитать официальное руководство по Ember JS.



Выбрав наиболее подходящий фреймворк и хорошенько ознакомившись с ним, стоит изучить идущий в паре с ним CSS-фреймворк. Два крупнейших игрока на этом рынке сегодня – Bootstrap и Material Design.



Bootstrap


Bootstrap сделали разработчики Twitter, и он уже довольно взрослый и популярный. Версии Bootstrap существуют для Angular, Angular 2 и React.



Material


Material – это набор правил дизайна, разработанный в Google. Он набирает популярность, существуют его версии для Angular и React. Поскольку Angular – это тоже детище Google, сочетается Material с ним превосходно.



Вот вам несколько ссылок:





Поздравляю! У вас есть ключевые навыки фронтенд-разработчика!





Вы только посмотрите на него!



Я хочу быть бэкенд-разработчиком



Прекрасно! Первый шаг – выбор языка. Для работы с бэкендом существует много языков, и у каждого есть свои преимущества и недостатки. Ознакомьтесь с таблицей языков программирования, отсортированных по популярности. Все они уже десять лет входят в десятку лучших. Отмеченные зелёным – это веб-языки, которые с годами набирают популярность.





TIOBE Index of Programming Languages, www.tiobe.com/tiobe_index?page=index



Концентрируйтесь на языках, чьи названия я обвёл зелёной рамкой. Если вы не знаете их, начинайте сверху таблицы и идите вниз. Останавливайтесь, когда найдёте то, что вам нравится, и копайте вглубь!



Если вы знакомы с одним из отмеченных зелёным цветом языков, и он вам нравится – концентрируйтесь на нём.



Java


Чрезвычайно популярный язык, запускающийся почти везде. Разработан в Sun Microsystems (сейчас им владеет Oracle). На этом языке пишутся приложения для Android. Его также можно использовать для создания десктопных приложений, и, конечно, веб-приложений (как отдельных приложений бэкенда, так и работающих в паре с JSP). Он развитый, стабильный, и для его изучения есть огромное количество ресурсов. Кроме того, это самый популярный язык для изучения объектно-ориентированного программирования в колледжах и университетах. Вот неплохой курс по Java для начинающих.



C#


C# был создан в компании Microsoft как прямой конкурент Java. До недавнего времени его поддержка на системах, не принадлежащих Microsoft, была не ахти – но сейчас ситуация выправляется. Как и Java, этот язык объектно-ориентирован, и может использоваться как для создания веб-приложений (как отдельно, так и совместно с ASP.Net), так и десктопных приложений. Если вы пользуетесь ОС Windows, и вам нужна более привычная среда разработки, C# может подойти вам. Ознакомьтесь с бесплатным курсом по языку от Microsoft Virtual Academy.



Python


За ним не стоит огромная компания, как за языками Java или C#, но Python – отличный язык для того, чтобы быстро выполнять поставленные задачи. Его относительно легко учить, и с каждым годом он набирает популярность. Если другие языки пришлись вам не по вкусу, вы можете углубиться в него. Лучше всего начать отсюда.



JavaScript


Если вы читаете эту статью с начала, то с JS вы уже разобрались. С пришествием Node.JS и популярностью npm (системы управления пакетами, Node Package Manager), серверный JavaScript несомненно будет и дальше набирать популярность. Стоит изучения.



Если вы раньше этого не сделали, сейчас самое время изучить Node.JS, Express и MongoDB при помощи этого превосходного бесплатного изучающего материала и его продолжения.



Ruby


Это странный язык. Те, кому он нравится, влюблены в него по уши. Он входит в десятку лучших, но рост его популярности замедляется. Он представляет собою гибрид между функциональным и императивным программированием. Рекомендую попробовать – вдруг вы станете одним из ярых его приверженцев. Рабочие места для Ruby будут существовать ещё долго. Но обязательно проверьте свой рынок вакансий, чтобы хоть какие-то компании были заражены интересом к Ruby.



Лучше всего учить Ruby на ресурсе RubyMonk



Что насчёт PHP?


На рынке полно вакансий для PHP, но, по крайней мере, судя по нашей табличке, он теряет популярность. Я выделил его жёлтым, поскольку это всё ещё неплохой выбор для создания карьеры. Если говорить честно, я сам не большой фанат PHP – возможно, это предубеждение играет свою роль. Но я не могу представить, чтобы через 5-10 лет PHP стал бы популярнее JavaScript, хотя сейчас он более популярен, и вакансии для PHP всё ещё будут существовать.



Я много чего изучил, но у меня нет реального опыта





Ну что, давайте наработаем вам опыт!



Без опыта работы очень сложно устроиться на работу. Сначала нужно закончить парочку персональных проектов, чтобы освоиться с их созданием от начала до конца. А когда вы начнёте работать над ними, очень полезным будет изучить способ управления и публикации кода. Для этого вам потребуется GitHub.



GitHub


GitHub это онлайн-репозиторий на платформе Git. Он позволяет хранить, управлять и публиковать код. Если вы уже работаете разработчиком, вы просто обязаны иметь там учётную запись. Изучить работу с GitHub вы сможете из этого обучающего материала «Hello World», а также из интерактивного обучающего материала по использованию платформы Git.



Личные проекты


Разобравшись с GitHub, нужно приступать к разработке своих проектов. И вот вам пара идей:


  • Сделайте простенький блог (вот вам обучалка для React и Node);

  • Сделайте простой календарь (обучалка для C# и .Net).





На ресурсе Free Code Camp вы найдёте разнообразные примеры проектов, включая те, что требуют только фронтенд. Два моих любимых, это:





Реальный опыт


Теперь вам нужен реальный опыт. Это значит, работать на кого-то ещё. Персональные проекты – вещь хорошая, но их недостаточно для того, чтобы успешно соревноваться на рынке работы. Для этого существует несколько вариантов.



1. Внесите вклад в проект с открытым кодом



Благодаря популярности GitHub существуют миллионы открытых проектов, в которых есть проблемы (ошибки), которые только и ждут, чтобы их исправил кто-то вроде вас. Включить в резюме упоминание об участии в известном открытом проекте – это отличный способ повысить ваш статус. Лучше всего найти себе проект по душе при помощи ресурса Code Triage. Он поможет выбрать наилучший проект для вас и будет отправлять вам задачи по почте каждый день.



2. Поработайте на знакомого или родственника



Выберите друга или родственника, владеющего своим предприятием, и сделайте для него веб-приложение или сайт, или улучшите существующий. Только выбирайте аккуратно – вам нужен проект подходящего масштаба. Правильный выбор родственника также поможет вам избежать неприятностей. В идеале вам нужен проект, который вы сможете сделать за срок менее 90 дней, и человек, с которым у вас очень честные взаимоотношения. Не бойтесь признаваться, когда что-то пошло не так, как было запланировано, и не бойтесь отказывать и давать отпор на определённые запросы или ожидания. Также убедитесь, что ваш отказ от проекта или отказ от него вашего партнёра не повлияет на ваши отношения.



3. Поработайте на благотворительные организации



Очень полезный способ набрать опыт – поработать на благотворительные и некоммерческие организации. Вы можете обратиться к милой вашему сердцу организации подобного рода и предложить свою помощь. Вы можете найти нужный проект через сайт Catch a Fire. А если вы полностью пройдёте программу обучения на сайте Free Code Camp и получите все сертификаты, вы получите доступ к некоммерческим проектам, где сможете применить ваши навыки.



4. Рабский труд



Дерзкий получился заголовок для следующего предложения – но, по-моему, если вы выберете этот вариант, а потом найдёте нормальную работу или пойдёте фрилансить, он покажется вам рабским трудом. На сайтах Upwork, Fiverr и PeoplePerHour можно преуспеть в роли разработчика, но вам придётся назначать очень маленькую плату и смириться с положением человека, просто зарабатывающего опыт.



У меня есть опыт, помогите мне с работой





Готовы бросить вызов миру?



Первое правило – не называйтесь «веб-разработчиком».







А что же это за разница такая между веб-разработчиком и разработчиком полного цикла? А вот получается, что разница составляет $7000 в год. Если серьёзно, простая смена названия может решать довольно много.



Сделайте приличное резюме


Несмотря на все весёлые истории про людей, которые вытворяют что-то невообразимое, чтобы привлечь внимание работодателя, вам всё равно необходимо будет подготовить хорошее резюме. Эта статья с ресурса Tech Republic поможет вам с этим.



Создайте веб-сайт с портфолио


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



Подготовьтесь для интервью


С этим вам поможет моя предыдущая статья How to Win the Coding Interview.



Подкачайте необходимые для интервью умения


Вам нужно подготовиться не только к написанию кода. В хорошей статье с Life Hacker описано много полезной и ценной информации.



Главное – закрепиться на рынке


Не сильно переживайте насчёт получения работы мечты у работодателя мечты с зарплатой мечты. Сначала просто добейтесь получения работы, где вы будете писать код за деньги. Получив больше опыта, сможете планировать следующий шаг.



Хочу быть фрилансером


Сам себе хозяин – это хорошо, но это одновременно и огромное давление, и большие сложности. Лучший источник информации по фрилансу из всех, что я видел — DoubleYourFreelancing.com. У него есть серия статей, которые помогут вам стать фрилансером лучше, чем это получилось бы у меня. Читайте.



Ещё один вариант, если вы в себе уверены – сервис Toptal. Они принимают лишь 3% из всех, кто подаёт заявки, и этот процесс очень сложен, но если вы попадёте туда – у вас будет доступ к хорошо оплачиваемым работам, над которыми вы сможете трудиться удалённо.



Я начал работу, но чувствую, что зашел в тупик


Понимаю. Это нелегко, и если кто-то говорил вам обратное – он либо этим не занимался, либо пытается выманить у вас деньги. Если вы чувствуете, что зашли в тупик, попробуйте следующие варианты:



Освежите ваше первоначальное намерение


Спросите себя, запишите на бумаге, почему вы решили идти по этому пути. В силе ли всё ещё ваш ответ? Если да – то зачем останавливаться? Вперёд!



Оцените свои реальные возможности


Теперь, когда вы знаете больше о том, что требуется для реализации ваших желаний, и что вы можете сделать – у вас есть хорошее представление о том, что может случиться в лучшем случае, в худшем случае. Поймите, что, скорее всего в жизни реализуется «наиболее вероятный» случай, который будет чуть ближе к «лучшему случаю», чем к «худшему». Вам не надо бояться – идите вперёд. У вас всё получится!



Прочтите следующую статью



"Не бросайте – каждый эксперт был когда-то новичком"
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303896/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Реализация интерактивных диаграмм с помощью ООП на примере прототипа редактора UML-диаграмм. Часть 1

Среда, 22 Июня 2016 г. 09:06 (ссылка)

С потребностью создания двумерных интерактивных графических компонент разработчикам программного обеспечения приходится сталкиваться довольно часто. Программисты, ранее привыкшие работать только с алгоритмами обработки данных, при возникновении подобных задач сталкиваются с большими трудностями, если только нельзя обойтись каким-нибудь совсем уж примитивным решением, вроде статической картинки с заранее определёнными «активными» областями. Нестандартность задачи многих отпугивает и заставляет искать готовые средства и библиотеки для отрисовки графов. Но сколь бы многофункциональной не была библиотека, для решения именно вашей задачи в ней будет чего-то недоставать.



В этой статье мы подробно разберём создание «с нуля» компоненты с интерактивными, «перетаскиваемыми» элементами в объектно-ориентированной среде разработки. В качестве примера мы построим прототип UML-редактора.





Постановка задачи



Основные требования к решению



Список задач, требующих реализации интерактивной графики, довольно обширен. Это могут быть


  • условные схемы оборудования (установок, конвейеров, транспортных систем), на которых отображается состояние работы узлов, «по щелчку мыши» пользователь желает получать дополнительную информацию или вводить управляющие команды,

  • аналитические графики (гистограммы, пузырьковые диаграммы), «по щелчку мыши» на элементы которых пользователь желает получать информацию, или же пользователь желает напрямую «подтаскивать» мышью элементы, изменяя картину в нужную сторону, чтобы узнать необходимые числовые показатели,

  • визуальный анализ данных, представимых в виде графов (например: взаимосвязи между юридическими лицами в базе данных), пользователь желает «перетаскивать» элементы графа вручную, чтобы в итоге вставить получившуюся картинку в печатный отчёт,

  • все средства визуального моделирования/конструирования чего-либо из блоков, в частности, все CASE-средства,



— и так далее, и тому подобное. Хотя внешний вид всех этих диаграмм различен, во всех случаях нужно реализовывать некоторые общие требования. Вот они:


  • Картинка должна состоять из дискретных элементов различной графической сложности,

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


  • Некоторые из элементов картинки должны быть «кликабельными», т. е. система в каждый момент должна «понимать», на какой именно элемент наведён указатель мыши, иметь возможность показывать для них всплывающие подсказки,




  • Некоторые из «кликабельных» элементов должны быть «выделяемыми», т. е. пользователь должен иметь возможность «поставить выделение» на элемент щелчком мыши, должно быть доступно выделение группы элементов с нажатой клавишей Shift и при помощи «прямоугольного лассо». С выделенными объектами, в зависимости от задачи, могут производиться некоторые действия или изменение свойств.

  • Некоторые из «кликабельных» элементов должны быть «перетаскиваемыми», т. е. пользователь должен иметь возможность передвинуть мышью один элемент или группу выделенных элементов:






Важность возможности «перетаскивания» элементов следует подчеркнуть особо. Если ваша задача включает в себя необходимость визуализации графов, нужно помнить: ни один из многочисленных алгоритмов автоматического расположения узлов графа на плоскости не может дать решение, полностью удовлетворительное во всех случаях, и для удобства пользователя «ручная» перестановка узлов графа просто необходима.

Какие же «ингриденты» нужны для приготовления этого «блюда»? В этой статье мы покажем общие принципы, которые можно применять в любой среде разработки при выполнении всего четырёх ключевых условий:


  1. Объектно-ориентированный язык программирования.

  2. Доступность объекта-«холста» (Canvas), с возможностью отрисовки графических примитивов (линий, дуг, многоугольников и т. п.).

  3. Компоненты, реализующие управляемые полосы прокрутки.

  4. Доступность обработки событий мыши.



Наш иллюстрирующий пример являет собой прототип редактора UML Use Case-диаграмм, мы будем пользоваться красивой диаграммой из этого руководства. Исходные коды нашего примера доступны по адресу http://inponomarev.ru/programming/graph.zip и могут быть скомпилированы при помощи Maven. Если вы хотите лучше усвоить изложенные в статье принципы, я настоятельно рекомендую скачать эти исходники и изучать их вместе со статьёй.



Пример построен на Java 8 со стандартной библиотекой Swing. Однако в изложенных принципах нет ничего Java-специфичного. Мы впервые реализовали изложенные тут принципы в Delphi (Windows-приложения), а затем в Google Web Toolkit (веб-приложения с выводом графики на HTML Canvas). При выполнении четырёх вышеуказанных условий предложенный пример можно сконвертировать и в другую среду разработки.



Трудности «наивного» подхода



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

canvas.drawOval(10, 0, 10, 10);
canvas.drawLine(15, 10, 15, 25);
canvas.drawLine(5, 15, 25, 15);
canvas.drawLine(5, 35, 15, 25);
canvas.drawLine(25, 35, 15, 25);




Но пока что наш «человечек» не «ожил»: его нельзя выделять, масштабировать, передвигать по холсту, он не умеет взаимодействовать с обществом других «человечков». Значит, надо написать код, ответственный за все эти операции. Для простой картинки это кажется несложным, однако по мере усложнения того, что мы хотим получить, нас поджидают проблемы.


  • С усложнением картины растёт длина «процедуры отрисовки». Для сложной схемы процедура становится очень длинной и запутанной.

  • Код, рисующий картинку, сам «по волшебству» не задаёт критерий, по которому можно было бы определить объект, выделяемый в текущий момент курсором мыши. Мы должны писать отдельную процедуру, определяющую объект, над которым находится курсор мыши, и при этом должны постоянно синхронизировать код процедуры отрисовки и процедуры распознавания объектов.

  • Вместе со сложностью процедуры отрисовки растёт сложность процедуры распознавания.



Попытка решить задачу «в лоб» усложнением процедур обречена на провал в силу быстрого усложнения исходного кода, количество которого будет расти лавинообразно по мере усложнения диаграммы. Однако применение объектно-ориентированной разработки, универсальный принцип «разделяй и властвуй», а также паттерны проектирования дают нам достаточно мощной инструментарий, чтобы изящно разделаться с перечисленными проблемами и реализовать нужную функциональность.



Итак, мы приступаем к решению.



Декомпозиция задачи. Структура классов



Начнём с того, что разобьём задачу на маленькие части, построив иерархический перечень того, что нам нужно нарисовать.

Сначала нарисуем картину, которую хотим получить, на доске или на бумаге:







И выстроим следующую иерархию:


  • Диаграмма целиком


    • Роли (Actors)


      • Подписи ролей


    • Варианты использования (Use Cases)

    • Наследования (generalizations)

    • Связи (associations)

    • Зависимости (dependencies)


      • Подписи стереотипов зависимостей









Наш пример, в действительности, очень прост, поэтому иерархия получилась неглубокой. Чем сложнее картинка, тем шире и глубже будет иерархия.



Обратите внимание на то, что некоторые пункты выделены курсивом. Это те объекты на диаграмме, которые мы хотим сделать выделяемыми и перемещаемыми при помощи курсора мыши.



Каждому из пунктов этой иерархии будет соответствовать класс-отрисовщик, а иерархическая связь между ними позволяет применить паттерн Composite — «компоновщик», который (цитирую книгу “Design Patterns”) «компонует объекты в древовидные структуры для представления иерархий часть-целое, позволяет… единообразно трактовать индивидуальные и составные объекты». Т. е. делает ровно то, что нам нужно.



На диаграмме классов наша система имеет следующий вид:





В верхней части диаграммы классов находятся два класса (DiagramPanel и DiagramObject), которые «ничего не знают» о конкретике отрисовываемой диаграммы и образуют фреймворк, на основе которого можно делать диаграммы различного вида. DiagramPanel (в нашем случае это наследник класса javax.swing.JPanel) представляет собой визуальный компонент интерфейса, ответственный за отображение диаграммы и её взаимодействие с пользователем. Объект DiagramPanel содержит в себе ссылку на DiagramObject — корневой объект-отрисовщик, соответствующий самому верхнему уровню иерархии отрисовки (в нашем случае это будет экземпляр класса UseCaseDiagram).



DiagramObject — это базовый класс всех объектов-отрисовщиков, реализующий их иерархию через паттерн Composite и многое другое, о чём речь пойдёт далее.



В нижней части находится пример использования фреймворка. Класс Example (наследник javax.swing.JFrame) — это главное окно приложения, которое в нашем примере содержит в себе в качестве одной единственной компоненты экземпляр DiagramPanel. Все прочие классы — наследники DiagramObject. Они соответствуют задачам в иерархическом перечне отрисовки. Обратите внимание, что иерархия наследования этих классов и иерархия отрисовки — это разные иерархии!



Иерархия отрисовки, в соответствии со сказанным выше, выглядит так:




  • UseCaseDiagram — диаграмма целиком,


    • DiagramActor — роль,


      • Label — подпись роли,


    • DiagramUseCase — вариант использования,

    • DiagramGeneralization — наследование,

    • DiagramAssociation — связь,

    • DiagramDependency — зависимость,


      • Label — подпись стереотипа зависимости.









Далее мы подробно опишем устройство классов DiagramObject и DiagramPanel и то, как их следует использовать.



Класс DiagramObject и его наследники



Структура данных



Класс DiagramObject устроен так, что внутри каждого из его экземпляров находится двусвязный список подчинённых отрисовщиков. Это достигается при помощи переменных previous, next, first и last, позволяющих ссылаться на соседние элементы в списках и иерархии. Когда объекты инстанцированы, получается примерно такая картина:







Эта, подобная простому двусвязному списку, структура данных хороша тем, что мы можем за время O(N) собрать нужную нам иерархию, а при необходимости — за время О(1) и модифицировать её, удалив заданный элемент или вставив новый в список после какого-либо заданного элемента. Доступ к элементам этой структуры нас интересует только последовательный, соответствующий обходу дерева в глубину, что достигается проходом по ссылкам. Движение по красным стрелкам соответствует обходу в прямую, а по синим стрелкам — обходу в обратную сторону.



Для добавления нового объекта во внутренний список DiagramObject служит метод addToQueue(DiagramObject subObj):

if (last!=null)) {
last.next = subObj;
subObj.previous = last;
} else {
first = subObj;
subObj.previous = null;
}
subObj.next = null;
subObj.parent = this;
last = subObj;




Чтобы собрать желаемую картину, остаётся лишь проинстанцировать нужное количество нужных отрисовщиков и объединить их в очереди в нужном порядке. В нашем примере большая часть этой работы происходит в конструкторе класса UseCaseDiagram:

DiagramActor a1 = new DiagramActor(70, 150, "Customer");
addToQueue(a1);
DiagramActor a2 = new DiagramActor(50, 350, "NFRC Customer");
addToQueue(a2);
DiagramActor a3 = new DiagramActor(600, 50, "Bank Employee");
addToQueue(a3);

DiagramUseCase uc1 = new DiagramUseCase(250, 50, "Open account");
addToQueue(uc1);
DiagramUseCase uc2 = new DiagramUseCase(250, 150, "Deposit funds");
addToQueue(uc2);

addToQueue(new DiagramAssociation(a1, uc1));
addToQueue(new DiagramAssociation(a1, uc2));

addToQueue(new DiagramDependency(uc2, uc5, DependencyStereotype.EXTEND));
addToQueue(new DiagramDependency(uc2, uc6, DependencyStereotype.INCLUDE));

addToQueue(new DiagramGeneralization(a2, a1));




В реальной жизни следует поступать, конечно, не так: вместо «зашивания в код» процесса создания объектов-отрисовщиков, в конструктор корневого класса вам необходимо будет передать модель данных вашей системы. Обходя уже в циклах объекты этой модели, вы будете создавать отрисовщики. Например, для каждого связанного с текущей диаграммой экземпляра класса Actor (соответствующего «роли» в вашей «модели документа UML») необходимо проинстанцировать объект класса-отрисовщика DiagramActor.



Удобно, когда отрисовщики хранят ссылки на соответствующие объекты модели. Передавать эти ссылки удобнее всего прямо в виде параметров конструкторов отрисовщиков. В нашем примере вместо них передаются мировые координаты объектов и их параметры, такие как название и стереотип.



Мировые и экранные координаты



Коль скоро мы применили термин «мировые координаты» — нужно уточнить, что это такое в нашем случае. «Мировые координаты» у нас — это координаты объектов диаграммы на «воображаемой миллиметровой бумаге», на которой умещается диаграмма целиком, которая имеет начало координат в левом верхнем углу и не подвергается никакому масштабированию. Мировые координаты совпадают с экранными, если масштаб картинки 1:1 и полосы прокрутки находятся в своих минимальных позициях. Мировая координата, в отличие от экранной, имеет не целочисленный тип, а принимает значение с плавающий точкой. Это нужно, чтобы не происходила пикселизация картинки при увеличении её масштабов. Например, хотя при масштабе 1:1 значение мировой координаты 0.3 не отличимо от нуля экранных пикселов, в масштабе 100:1 оно превращается уже в 30 экранных пикселов.



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



Для перевода мировых координат в экранные класс DiagramObject содержит важные методы scaleX(…), scaleY(…) и просто scale(…). Первые два применяют к мировой координате масштабный коэффициент и учитывают сдвиг горизонтальной и вертикальной полосы прокрутки, соответственно. Последний метод, scale(…), применяет масштабный коэффициент, но не учитывает сдвиг: он необходим для расчёта не позиции, а размера (например, ширины прямоугольника или радиуса окружности).



Отрисовка диаграммы с точки зрения DiagramObject. Самостоятельные, полу-самостоятельные и зависимые объекты





Для отрисовки диаграммы вызывается метод draw(Graphics canvas, double aDX, double aDY, double scale) корневого DiagramObject. Его параметрами являются:


  • canvas — контекст рисования

  • aDX, aDY — положения полос прокрутки

  • scale — масштаб (1.0 — для масштаба 1:1, больше/меньше — для увеличения/уменьшения).



Этот метод реализует паттерн проектирования Template Method (шаблонный метод) и выглядит следующим образом:

this.canvas = canvas;
this.scale = scale;
dX = aDX;
dY = aDY;
saveCanvasSetup();
internalDraw(canvas);
restoreCanvasSetup();
DiagramObject curObj = first;
while (assigned(curObj)) {
curObj.draw(canvas, aDX, aDY, scale);
curObj = curObj.next;
}




Т. е. метод draw(…):


  • запоминает в полях объекта параметры (они затем неоднократно используются разными методами),

  • сохраняет с помощью saveCanvasSetup() все настройки контекста отрисовки (цвет, перья, размер шрифта и т. п.),

  • вызывает метод internalDraw(), который на уровне DiagramObject не делает ничего, а в его наследниках переопределяется процедурой отрисовки объекта,

  • восстанавливает с помощью restoreCanvasSetup() настройки, которые могли быть нарушены после выполнения internalDraw,

  • пробегает по очереди всех своих подобъектов и вызывает метод draw для каждого из них.



Таким образом, инвариантная часть алгоритма реализована в методе draw(…), а изменяемая часть (собственно рисование) реализуется в классах-наследниках, что и составляет суть паттерна Template Method.



Предназначение методов saveCanvasSetup() и restoreCanvasSetup() — сохранить состояние контекста рисования, так чтобы каждый из объектов-отрисовщиков получил его в «нетронутом» виде. Если эти методы не применять, и в одном из наследников-отрисовщиков, допустим, цвет чернил изменить на красный, то всё, что будет нарисовано далее, будет нарисовано красным цветом. Реализация данных методов зависит от вашей среды разработки и возможностей, предоставляемых механизмом рисования. В Delphi и Java Swing, к примеру, надо сохранять множество параметров контекста, а в HTML Canvas2D специально для этой цели имеются готовые методы save() и restore(), сразу сохраняющие в специальный стек всё состояние контекста.



Вот как выглядит метод internalDraw в классе DiagramActor (сравните с «наивным примером», с которого мы начали):

static final double ACTOR_WIDTH = 25.0;
static final double ACTOR_HEIGHT = 35.0;

@Override
protected void internalDraw(Graphics canvas) {
double mX = getmX();
double mY = getmY();
canvas.drawOval(scaleX(mX + 10 - ACTOR_WIDTH / 2), scaleY(mY + 0 - ACTOR_HEIGHT / 2), scale(10), scale(10));
canvas.drawLine(scaleX(mX + 15 - ACTOR_WIDTH / 2), scaleY(mY + 10 - ACTOR_HEIGHT / 2),
scaleX(mX + 15 - ACTOR_WIDTH / 2), scaleY(mY + 25 - ACTOR_HEIGHT / 2));
canvas.drawLine(scaleX(mX + 5 - ACTOR_WIDTH / 2), scaleY(mY + 15 - ACTOR_HEIGHT / 2),
scaleX(mX + 25 - ACTOR_WIDTH / 2), scaleY(mY + 15 - ACTOR_HEIGHT / 2));
canvas.drawLine(scaleX(mX + 5 - ACTOR_WIDTH / 2), scaleY(mY + 35 - ACTOR_HEIGHT / 2),
scaleX(mX + 15 - ACTOR_WIDTH / 2), scaleY(mY + 25 - ACTOR_HEIGHT / 2));
canvas.drawLine(scaleX(mX + 25 - ACTOR_WIDTH / 2), scaleY(mY + 35 - ACTOR_HEIGHT / 2),
scaleX(mX + 15 - ACTOR_WIDTH / 2), scaleY(mY + 25 - ACTOR_HEIGHT / 2));
}




В точке (mX, mY) находится середина объекта. Т. к. начало координат «наивного примера» находится в левом верхнем углу, их необходимо сместить на половину ширины и половину высоты объекта. «Наивный пример» не учитывал необходимость масштабирования и смещения картинки, мы же учитываем это, переводя мировые координаты в экранные при помощи методов scaleX(…), scaleY(…) и scale(…).



Обекты DiagramActor и DiagramUseCase полностью «самостоятельны», их позиции целиком определяются внутренним состоянием, хранимым в полях mX и mY. В то же время всевозможные соединительные стрелки собственного состояния не имеют — их позиция на экране полностью определена позициями объектов, которые они соединяют, они полностью «не самостоятельны», они проходят по прямой, соединяющей центры объектов:







И отдельно следует обратить внимание на подписи к объектам. В своём внутреннем состоянии они хранят не абсолютные координаты, а смещение относительно родительского объекта-отрисовщика, поэтому они ведут себя «полу-самостоятельно»:







Определение объекта под курсором мыши



Разобравшись с отрисовкой, переходим к вопросу о том, каким же образом диаграмма «понимает», на какой объект мы кликнули мышью. Оказывается, что задача определения объекта, находящегося под курсором мыши, очень похожа на задачу отрисовки и в определённом смысле симметрична ей.



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



Например, для DiagramActor речь идёт о попадании в прямоугольную область:

	protected boolean internalTestHit(double x, double y) {
double dX = x - getmX();
double dY = y - getmY();
return dY > -ACTOR_HEIGHT / 2 && dY < ACTOR_HEIGHT / 2
&& dX > -ACTOR_WIDTH / 2 && dX < ACTOR_WIDTH / 2;
}


Для DiagramUseCase речь идёт о попадании в область, имеющую вид эллипса:

	protected boolean internalTestHit(double x, double y) {
double dX = 2 * getScale() * (x - getmX()) / (width + 2 * MARGIN / getScale());
double dY = 2 * (y - getmY()) / HEIGHT;
return dX * dX + dY * dY <= 1;
}


Теперь, если мы хотим определить объект, над которым сейчас находится курсор, мы можем методом последовательного перебора вызывать internalTestHit для каждого из объектов диаграммы, и первый, вернувший true, окажется искомым объектом. Только делать это надо в порядке, обратном порядку отрисовки (движение по синим стрелкам на иллюстрации, показывающей структуру данных)! Если курсор мыши находится в области, на которой пересекается несколько объектов, именно поиск в обратном порядке обеспечит попадание курсором в объект, отрисованный позже других, т. е. визуально находящийся «над другими».







Вот как это реализовано в ещё одном шаблонном методе DiagramObject:

public final DiagramObject testHit(int x, int y) {
DiagramObject result;
DiagramObject curObj = last;
while (assigned(curObj)) {
result = curObj.testHit(x, y);
if (assigned(result))
return result;
curObj = curObj.previous;
}
if (internalTestHit(x / scale + dX, y / scale + dY))
result = this;
else {
result = null;
}
return result;
}




Объект DiagramPanel вызывает метод testHit корневого объекта отрисовки. Во время его выполнения происходит рекурсия, выполняющая обход дерева отрисовки в глубину в направлении, противоположном направлению отрисовки. Возвращается первый найденный объект: это и будет самый «верхний» с точки зрения пользователя объект, находящийся под курсором мыши.



Определение текущего контекста под курсором мыши



Объект, находящийся под курсором мыши, может оказаться лишь составной частью более крупного объекта и не иметь самостоятельного значения. Если мы хотим произвести некоторую операцию над объектом и кликнули мышью на его часть, то операцию всё равно нужно производить над родительским объектом. Правильно показать контекст можно при помощи делегирования — приёма, связанного с использованием паттерна Composite (см. на этот счёт книгу Design Patterns). В нашем примере мы применяем делегирование для получения всплывающей подсказки объекта: например, если пользователь наводит курсор мыши на подпись под Actor-ом, он получает ту же подсказку, что и при наведении курсора собственно на Actor-а.



Идея очень проста: метод getHint() класса DiagramObject выполняет следующее: если его собственная реализация метода internalGetHint() в состоянии вернуть строку-подсказку — то она же и возвращается. Если не в состоянии, то идёт обращение к родительскому (в иерархии отрисовки) объекту — не может ли он выполнить работу метода getHint(). В случае, если и он «не берётся», «передача ответственности» будет продолжаться до самого корневого объекта-отрисовщика. Помимо механизма делегирования, мы вновь применяем паттерн Template Method:

	public String getHint() {
StringBuilder hintStr = new StringBuilder();
if (internalGetHint(hintStr))
return hintStr.toString();
else if (assigned(parent))
return parent.getHint();
else {
return "";
}
}
protected boolean internalGetHint(StringBuilder hintStr) {
return false;
}


Вспомогательные методы DiagramObject



Наследники DiagramObject могут переопределить следующие методы — их использование в классе DiagramPanel станет понятно из дальнейшего:




  • boolean isCollectable() — можно ли будет захватить объект с помощью «лассо» (прямоугольного выделения). Используется механизмами DiagramPanel, о которых речь пойдёт далее

  • boolean isMoveable() — является ли объект перемещаемым с помощью Drag and Drop. В нашем примере узлы диаграммы (Actor и UseCase) являются перемещаемыми и захватываемыми при помощи лассо, а соединительные линии (Association, Generalization, Dependency) таковыми не являются.

  • double getMinX(), getMinY(), getMaxX(), getMaxY() — мировые координаты самой левой, самой верхней, самой правой и самой нижней точки объекта. Нужны, во-первых, для корректной работы прямоугольного выделения (чтобы выделить объект, нужно захватить его целиком), а во-вторых, они используются в дефолтной реализации метода internalDrawSelection(), чтобы нарисовать выделение объекта по его углам.

  • final int minX(), minY(), maxX(), maxY() — то же самое, но уже переведённое в экранные координаты (не переопределяемые методы).



Отрисовка выделения



И наконец, ещё одна важная функциональность, реализованная на уровне DiagramObject, которая может быть переопределена в его наследниках — отрисовка выделения, т. е. графической метки, по которой пользователь может понять, что объект находится в выделенном состоянии. По умолчанию это четыре синие квадратные точки по углам объекта:



private static final int L = 4;

protected void internalDrawSelection(Graphics canvas, int dX, int dY) {
canvas.setColor(Color.BLUE);
canvas.setXORMode(Color.WHITE);
canvas.fillRect(minX() + dX - L, minY() + dY - L, L, L);
canvas.fillRect(maxX() + dX, minY() + dY - L, L, L);
canvas.fillRect(minX() + dX - L, maxY() + dY, L, L);
canvas.fillRect(maxX() + dX, maxY() + dY, L, L);
canvas.setPaintMode();
}


Обратите внимание на целочисленные (а значит, в экранных координатах) параметры dX, dY и на вызов setXORMode(), переключающий контекст отрисовки в «XOR-режим»: в этом режиме для того, чтобы стереть ранее нарисованное изображение, достаточно прорисовать его ещё раз. Это нужно для того, чтобы реализовать Drag&Drop для объектов диаграммы: для простоты, мы «перетаскиваем» мышью не само изображение, а его выделение, и затем уже перебрасываем изображение на новое место, при этом в параметрах dX, dY будет передано смещение объекта в экранных координатах относительно исходного положения:





Если такое поведение системы не устраивает, то можно переопределить метод internalDrawSelection в наследниках класса DiagramObject, чтобы рисовать в качестве выделения (и передвигать при drag&drop) что-нибудь более сложное.



* * *



Это всё, что касается класса DrawObject. Во второй части статьи будет рассмотрено построение класса DiagramPanel, отвечающего за обработку событий мыши и масштабирование, панорамирование, выделение объектов и drag&drop. Полный исходный код нашего примера, напоминаю, доступен по адресу http://inponomarev.ru/programming/graph.zip и может быть скомпилирован при помощи Maven.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303794/

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Тестируем асинхронный код

Вторник, 21 Июня 2016 г. 14:44 (ссылка)

Асинхронный код – сложный. Все это знают. Писать асинхронные тесты – еще сложнее. Недавно я починил мигающий (flaky) тест и хотел бы поделиться некоторыми мыслями по поводу написания асинхронных тестов.



В этой статье мы разберем общую для асинхронных тестов проблему – как выстроить потоки в определенном порядке, заставляя отдельные операции отдельных потоков выполняться раньше других операций других потоков. В обычных условиях мы не стремимся выстраивать принудительный порядок исполнения разных потоков, поскольку это противоречит самой причине использования потоков, которая заключается в том, чтобы обеспечить параллельность и позволить процессору выбирать наилучший порядок исполнения, исходя из имеющихся в наличии ресурсов и состояния приложения. Но определённый порядок может потребоваться во время тестирования для обеспечения стабильности тестов.







Тестируем throttler



Throttler (Ограничитель) – это класс, отвечающий за ограничение количества одновременных операций, выполняемых с некоторым ресурсом (например, пул соединения, сетевой буфер или ресурсоемкие операции процессора). В отличие от других инструментов синхронизации, роль ограничителя заключается в том, чтобы запросы, превышающие квоту, завершались ошибкой немедленно, без ожидания. Быстрое завершение важно, поскольку альтернатива, ожидание, потребляет ресурсы – порты, потоки и память.



Вот простая реализация ограничителя (в своей основе это обертка вокруг класса Semaphore; в реальном мире тут может встречаться ожидание, повторные попытки и т.д.):

class ThrottledException extends RuntimeException("Throttled!")
class Throttler(count: Int) {
private val semaphore = new Semaphore(count)
def apply(f: => Unit): Unit = {
if (!semaphore.tryAcquire()) throw new ThrottledException
try {
f
} finally {
semaphore.release()
}
}
}


Начнем с простого юнит-теста: тестируем ограничитель для одного потока (для тестов мы используем библиотека specs2). В этом тесте мы проверяем, что можем выполнить больше последовательных вызовов, чем установленное для ограничителя максимальное количество одновременных вызовов (переменная maxCount ниже). Заметьте, что поскольку мы используем единственный поток, мы не проверяем способность ограничителя к «быстрому завершению», так как мы не нагружаем ограничитель. По сути, мы проверяем лишь тот факт, что пока ограничитель не нагружен, он не прерывает выполнение операций.

class ThrottlerTest extends Specification {
"Throttler" should {
"execute sequential" in new ctx {
var invocationCount = 0
for (i <- 0 to maxCount) {
throttler {
invocationCount += 1
}
}
invocationCount must be_==(maxCount + 1)
}
}
trait ctx {
val maxCount = 3
val throttler = new Throttler(maxCount)
}
}




Тестируем ограничитель асинхронно



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



Подготовка:

val e = Executors.newCachedThreadPool()
implicit val ec: ExecutionContext = ExecutionContext.fromExecutor(e)
private val waitForeverLatch = new CountDownLatch(1)

override def after: Any = {
waitForeverLatch.countDown()
e.shutdownNow()
}

def waitForever(): Unit = try {
waitForeverLatch.await()
} catch {
case _: InterruptedException =>
case ex: Throwable => throw ex
}


Объект ExecutionContext используется для конструкции Future; метод waitForever держит поток до момента, пока не обнулится счетчик waitForeverLatch – перед окончанием теста. В следующей за этим функции мы закрываем ExecutorService.



Упрощенный способ проверки многопоточного поведения ограничителя выглядит так:

"throw exception once reached the limit [naive,flaky]" in new ctx {
for (i <- 1 to maxCount) {
Future {
throttler(waitForever())
}
}
throttler {} must throwA[ThrottledException]
}


Здесь мы создаем потоки в количестве равном maxCount. В каждом потоке мы вызываем функцию waitForever, которая ждет до окончания теста. Затем мы пытаемся выполнить еще одну операцию, чтобы обойти ограничитель – maxCount +1. Предполагается, что в этом месте мы должны получить исключение ThrottledException. Однако, хотя мы ждем исключения, оно не наступает. Последний вызов ограничителя (с ожиданием) может произойти до запуска любого из future (это приводит к тому, что исключение бросается в этом экземпляре future, но не в рамках ожидания).



Проблема с вышеприведенным тестом в том, что мы не знаем о запуске всех потоков и ждем в функции waitForever до того, как попробуем обойти ограничитель, ожидая, что ограничитель бросит исключение. Чтобы исправить это, нам нужно каким-то образом подождать, пока все потоки future будут запущены. Вот подход, знакомый многим из нас: просто добавить вызов метода sleep с некоей разумной длительностью.

"throw exception once reached the limit [naive, bad]" in new ctx {
for (i <- 1 to maxCount) {
Future {
throttler(waitForever())
}
}
Thread.sleep(1000)
throttler {} must throwA[ThrottledException]
}


Хорошо, теперь наш тест будет почти всегда проходить. Но это неправильный подход, как минимум по двум причинам:

Длительность теста будет в точности равна установленной нами «разумной длительности».

В очень редких ситуациях, например, при высокой загруженности машины, этой разумной длительности может не хватить.



Если вы все еще сомневаетесь, поищите в Google иные причины.

Более правильный подход заключается в синхронизации старта наших потоков (future) и нашего ожидания.



Будем использовать класс CountDownLatch из пакета java.util.concurrent:

"throw exception once reached the limit [working]" in new ctx {
val barrier = new CountDownLatch(maxCount)

for (i <- 1 to maxCount) {
Future {
throttler {
barrier.countDown()
waitForever()
}
}
}

barrier.await(5, TimeUnit.SECONDS) must beTrue

throttler {} must throwA[ThrottledException]
}


Мы используем CountDownLatch для барьерной синхронизации. Метод await блокирует главный поток до обнуления счетчика latch. При запуске других потоков (будем обозначать эти другие потоки как futures), каждый из этих futures вызывает барьерный метод countDown, чтобы снизить значение счетчика latch на единицу. Когда счетчик latch становится равным нулю, все futures располагаются внутри метода waitForever.

К этому моменту мы убедились, что ограничитель нагружен и содержит количество потоков, равное maxCount. Попытка другого потока задействовать ограничитель приведет к исключению. Таким образом, мы получили детерминированный порядок исполнения, при котором мы можем проверить поведение ограничителя в главном потоке. Главный поток может и будет продолждать выполнение в этой точке (счетчик barrier достигает нуля и CountDownLatch освобождает ожидающий поток).

Мы используем несколько завышенный таймаут во избежание бесконечной блокировки, если произойдет что-то непредвиденное. Если что-то произойдет – тест упадет. Этот таймаут не повлияет на длительность теста, поскольку, если ничего непредвиденного не произойдет, нам не надо его ждать.



Напоследок



При тестировании асинхронного кода достаточно часто есть потребность в определенном порядке потоков для определенного теста. Если не применять никакой синхронизации, получим нестабильные тесты, которые иногда отрабатывают, а иногда падают. Использование Thread.sleep снижает нестабильность тестов, но не решает проблемы. В большинстве случаев, когда нам необходимо определять порядок потоков в тесте, мы можем использовать CountDownLatch вместо Thread.sleep. Преимущество CountDownLatch в том, что мы можем указать, когда сбросить ожидание (удержание) потока, что дает нам два важных преимущества: детерминированное определение порядка и, благодаря этому, более стабильные тесты и более быстрое прохождение тестов. Даже для обычного ожидания, например, функции waitForever, мы могли бы использовать что-нибудь вроде Thread.sleep(Long.MAX_VALUE), но ненадежных подходов лучше всегда избегать.



Разработчик конструктора сайтов Wix,

Дмитрий Команов

Оригинал статьи: блог инженеров компании Wix
Original source: habrahabr.ru.

https://habrahabr.ru/post/301644/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Дайджест интересных событий из мира Java, и вокруг нее #4 (06.06.2016 — 19.06.2016)

Воскресенье, 19 Июня 2016 г. 22:03 (ссылка)

image



В этом выпуске



— Не успеваем: дедлайны Java 9 снова сдвигаются

— Спасаем Java EE: петиция Ларри Элиссону

Microsoft покупает LinkedIn

— Зачем отключать C2-компиляцию?

— Холивар: использовать assert или нет?

… и многое другое



1. Новости



1.1. Java 9: сроки плывут

Ссылка: http://mail.openjdk.java.net/pipermail/jdk9-dev/2016-June/004443.html



Главный архитектор Java Марк Рейнхольд нампомнил, что запланированные сроки «feature complete» майлстоуна уже прошли, но ряд фич Java 9 по прежнему не готовы. Речь идет о примерно 15 JEP-ах. В письме предлагается не поднимать панику, а планомерно завершать работу, ибо главное это фактическая готовность, и качество, а не формальные сроки.



image



Разумный подход. Однако стоит напомнить, что это не первый косяк со сроками. Все мы прекрасно знаем, насколько сложно укладываться в дедлайны при разработке. Но Java — это публичный продукт, за развитием которого следят миллионы разработчиков. Массовый пользователь не будет вникать, что такое Оракловский майлостоун, и что, дескать, он слабо привязан к датам. Это внутренняя кухня Oracle. Нам сказали «будет к такому-то числу», мы запасаемся попкорном, и ждем. Если обещанного не происходит, мы расстраиваемся, и начинаем перемывать косточки. Учитывая, продолжающиеся срывы сроков, Oracle имеет смысл поднажать на developer relations, что бы минимизировать репутационные издержки.



1.2. Спасаем Java EE: петиция Ларри Элиссону

Ссылка: https://www.change.org/p/larry-ellison-tell-oracle-to-move-forward-java-ee-as-a-critical-part-of-the-global-it-industry



Вокруг Java EE продолжают кипеть страсти. Группа людей, называющая себя Java EE Guardians, подала петицию на имя CEO Oracle Ларри Элиссона, в надежде привлечь внимание топ-менеджмента компании к заморозке развития Java EE.



Лично мне тоже не совсем понятна политика Oracle по поводу Java EE. Очень интересно было бы послушать их комментарии. Если вы где-то их встречали, поделитесь пожалуйста ссылкой.



1.3. Microsoft покупает LinkedIn

Ссылка: http://www.cnbc.com/2016/06/13/microsoft-to-buy-linkedin.html



Сумма сделки, как это водится в IT-мире, достаточно скромная — каких-то 26.2 миллиарда долларов. Теперь детище Билла Гейтца получит контроль над огромным количеством внутренних Java-проектов, созданных в LinkedIn за эти годы. Не исключено, что какие-то из этих наработок мигрируют в новые проекты Microsoft.



Так же сделка дала многим возможность пощеголять чувством юмора:

According to Microsoft, LinkedIn is worth ~2 of SpaceX.

One is going to Mars, the other is going to spam.

— Brian Krogsgard (@Krogsgard) 13 июня 2016 г.



image



2. Почитать



2.1. Сборник материалов по внутренностям HotSpot

Ссылка: https://wiki.openjdk.java.net/display/HotSpot/Presentations



Архитектор JVM John Rose занялся сбором воедино материалов по внутренностям JVM. Пока там буквально несколько ссылок, но я надеюсь, что в будущем этот список будет расти. Так что добавляйте ссылку в «Избранное».



2.2. Статическим анализатором по OpenJDK

Ссылка: https://medium.com/@Coder_HarryLee/openjdk-check-by-pvs-studio-f25a2187b8a0#.obyfqnjbs



Парни прошлись статическим анализаторам PVS Studio по исходникам OpenJDK, и нашли там приличное количество проблем. Кривые проверки в if-else выражениях, забытые скобки, копипаста, отсутствующие проверки на NULL, и т.д… Можно сколь угодно скептически относится к анализаторам кода, но эти бездушные машины не зря едят свой хлеб.



2.3. (Не) используйте assert!

Ссылка: http://www.yegor256.com/2016/06/17/dont-use-java-assertions.html



Егор Бугаенко стремительным домкратом промчался по европейским конференцям, наделав много шуму своими радикальными взглядами на ООП. Досталось всем — и Хибернейту, и аннотациям, и много кому еще. Печальная участь постигла и assert-ы. В своем свежем посте Егор призывает отказаться от их использования, и полагаться исключительно на exceptions.



Взаимоотношения разработчиков и assert никогда не были простыми. Для одних это «активная» документация и халявные дополнительный «тесты». Для других это умышленная маскировка багов. Лично я assert люблю, и в нашем проекте их очень много. Есть два простых правила: не проверять ими пользовательский ввод, и не заменять ими тесты. Следуйте этим правилам, и assert станет Вашим надежным помощником.



Что думаете по этому поводу? Используете ли Вы assert в своих проектах?



2.4. Уменьшаем memory footrpint Java-приложения

Ссылка: https://blog.buoyant.io/2016/06/17/small-memory-jvm-techniques-for-microservice-sidecars/



У парней из buoyant.io один из продуктов стартует вспомогательные Java-процессы. И им очень хотелось, что бы эти процессы потребляли как можно меньше памяти. В итоге они добились желаемых результатов путем:

— Использования 32-битного режима

— Отключения C2



Второй подход вызывает вопросы, но если С1 выдает им достаточную производительность — то почему бы и нет?



3. Мудрость



3.1. Дизайн API

A lot of performance is dictated or bounded by API design, but it’s not often considered when designing them. Can often tell when it is, tho

— Chris Vest (@chvest) 8 июня 2016 г.

Бросить exception или вернуть код ошибки? Отдать пользователю коллекцию или итератор? Потоковая обработка или модель в памяти? Вопросов много, вариантов решения еще больше. Создание хорошего API — невероятно сложная работа.



3.2. Внешние зависимости в проекте

"Zero-dependency library" aka. I preferred to code a bug ridden variant of some functionality already available out there…

— Oliver Gierke (@olivergierke) 13 июня 2016 г.

Мысль, которая может быть спасительной для одних проектов, и губительной для других.



3.3. Как помочь open source проекту?

Inconvenient truth: one of the most useful contributions you can make to open source software is good documentation.

— Richard Astbury (@richorama) 14 июня 2016 г.

Утверждение справедливо для любых проектов, не только open source. Все понимают, насколько важна хорошая документация. Но время на ее написание мы обычно находим с большим трудом.



3.4. О функциональном программировании

Use Java 8 features and functional programming in general to improve readability, not just to save characters. Characters are cheap

— Mario Fusco (@mariofusco) 10 июня 2016 г.



4. Юмор



4.1.Пожалуй, лучшее объяснение, что такое map-reduce

Map/filter/reduce in a tweet:

map([, , ], cook)
=> [, , ]

filter([, , ], isVegetarian)
=> [, ]

reduce([, ], eat)
=>

— Steven Luscher (@steveluscher) 10 июня 2016 г.



4.2. Микросервисы против монолитов

Hadi Hariri из JetBrains достаточно доходчиво разъясняет ключевые отличия этих двух архитектурных подходов:image



4.3. Если Вы собрались навести порядок в своем проекте ...

… то помните, что возможно кто-то уже делал это до Вас.

image

Источник: https://twitter.com/swardley



Предыдущие выпуски

#3 (23.05.2016 — 05.06.2016)

#2 (09.05.2016 — 22.05.2016)

#1 (02.05.2016 — 08.05.2016)
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/303604/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Проверка проекта OpenJDK с помощью PVS-Studio

Пятница, 17 Июня 2016 г. 13:02 (ссылка)

Соавтор: Роман Фомичёв.



В настоящее время многие проекты открывают свой исходный код и разрешают делать изменения в нем сообществу заинтересованных разработчиков. Мы проверим один из таких проектов — OpenJDK, и поможем разработчикам улучшить их код.



Введение



OpenJDK (Open Java Development Kit) — проект по созданию реализации платформы Java (Java SE), состоящий исключительно из свободного и открытого исходного кода. Проект стартовал в 2006 году усилиями компании Sun. В проекте используются несколько языков — C, C++ и Java. Нас интересуют исходные коды написанные на С и С++. Для проверки возьмем 9-ю версию OpenJDK. Код этой реализации Java платформы доступен в репозитории Mercurial.



Проект был проверен с помощью статического анализатора кода PVS-Studio. В нем реализовано множество диагностических правил, позволяющих найти большое количество ошибок, допущенных при программировании, в том числе и таких, которые трудно обнаружимы при простом просмотре кода. Некоторые из этих ошибок не влияют на логику работы программы, а некоторые могут привести к печальным последствиям при исполнении программы. На сайте анализатора есть примеры ошибок, найденных в других проектах. Для анализа доступны проекты, использующие языки C, C++ и С#. Загрузить пробную версию анализатора можно по ссылке.



Ошибки в логических выражениях







Сначала рассмотрим ошибки в логических выражениях:

int StubAssembler::call_RT(....) {
#ifdef _LP64
// if there is any conflict use the stack
if (arg1 == c_rarg2 || arg1 == c_rarg3 ||
arg2 == c_rarg1 || arg1 == c_rarg3 ||
arg3 == c_rarg1 || arg1 == c_rarg2) {
....
}


Предупреждение PVS-Studio: V501 There are identical sub-expressions 'arg1 == c_rarg3' to the left and to the right of the '||' operator. c1_Runtime1_x86.cpp 174



Анализатор сообщает о дублировании проверки arg1 == c_rarg3. В этом фрагменте или избыточная проверка или, что гораздо хуже, присутствует логическая ошибка. Возможно, вместо продублированного условия должно было проверяться что-то другое. Разработчикам есть смысл взглянуть повнимательнее на этот код.



В этом же условии есть еще одно повторяющееся выражение arg1 == c_rarg2:



Предупреждение PVS-Studio: V501 There are identical sub-expressions 'arg1 == c_rarg2' to the left and to the right of the '||' operator. c1_Runtime1_x86.cpp 174



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



В следующем фрагменте встретилась «неидеальная» проверка в условии метода Ideal:

Node *AddLNode::Ideal(PhaseGVN *phase, bool can_reshape) {
....
if( op2 == Op_AddL &&
in2->in(1) == in1 &&
op1 != Op_ConL &&
0 ) {
....
}


Предупреждение PVS-Studio: V560 A part of conditional expression is always false: 0. addnode.cpp 435



Довольно странно в логическом выражении использовать 0. Скорее всего, этот фрагмент кода ещё в разработке и с целью отладки условие сделали невыполнимым. Соответствующие комментарии в коде отсутствуют, и в этом случае высока вероятность забыть исправить код в дальнейшем. Результатом этой ошибки будет то, что все, что находится внутри этого условия, никогда не будет исполняться, так как результат вычисления логического выражения будет всегда false.



Приоритеты операций



Довольно часто программисты надеются на свое знание приоритетов и не выделяют составные части сложного выражения скобками:

int method_size() const
{ return sizeof(Method)/wordSize + is_native() ? 2 : 0; }


Предупреждение PVS-Studio: V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '+' operator. method.hpp 249



В данном случае я не знаю специфики кода, но есть подозрение, что в функции планировали выбирать значение смещения '2' или '0' в зависимости от результата вызова функции is_native(), но выражение имеет иной порядок вычисления. Сначала выполнится сложение sizeof(Method)/wordSize + is_native(), а потом уже будет возвращен результат 2 или 0. Скорее всего, код должен выглядеть так:

{ return sizeof(Method)/wordSize + (is_native() ? 2 : 0); }


Это очень распространённая ошибка с приоритетом операций. В базе найденных анализатором ошибок были выявлены самые популярные из них и приведены в статье: Логические выражения в C/C++. Как ошибаются профессионалы.



Copy-Paste







Следующая характерная группа ошибок связана с копированием кода. От этого излюбленного приема программистов никуда не денешься, поэтому исследуем места, где был применен copy paste:

static int
setImageHints(....)
{
....
if (dstCMP->isDefaultCompatCM) {
hintP->allocDefaultDst = FALSE;
hintP->cvtToDst = FALSE;
}
else if (dstCMP->isDefaultCompatCM) {
hintP->allocDefaultDst = FALSE;
hintP->cvtToDst = FALSE;
}
....
}


Предупреждение PVS-Studio: V517 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. Check lines: 1873, 1877. awt_ImagingLib.c 1873



В этом примере полностью совпадают как условия в if и else if, так и тот код, который должен исполняться. Второе условие полностью бессмысленно, оно никогда не будет исполняться.



Еще один похожий случай:

static int expandPackedBCR(JNIEnv *env, RasterS_t *rasterP, 
int component,
unsigned char *outDataP)
{
....
/* Convert the all bands */
if (rasterP->numBands < 4) {
/* Need to put in alpha */
for (y=0; y < rasterP->height; y++) {
inP = lineInP;
for (x=0; x < rasterP->width; x++) {
for (c=0; c < rasterP->numBands; c++) {
*outP++ = (unsigned char)
(((*inP&rasterP->sppsm.maskArray[c]) >> roff[c])
<scanlineStride;
}
}
else {
for (y=0; y < rasterP->height; y++) {
inP = lineInP;
for (x=0; x < rasterP->width; x++) {
for (c=0; c < rasterP->numBands; c++) {
*outP++ = (unsigned char)
(((*inP&rasterP->sppsm.maskArray[c]) >> roff[c])
<scanlineStride;
}
}
....
}


Предупреждение PVS-Studio: V523 The 'then' statement is equivalent to the 'else' statement. awt_ImagingLib.c 2927



Исполняемый код в обоих блоках идентичен, соответственно нет никакой разницы, что рассчитывается в условии. Есть смысл взглянуть на это место и убрать ненужную ветку или, если подразумевалась разная логика, скорректировать код, чтобы исключить дублирование.



Есть еще два места с идентичным дублированием. Просто укажу их, без приведения кода:

  • V523 The 'then' statement is equivalent to the 'else' statement. awt_ImagingLib.c 3111

  • V523 The 'then' statement is equivalent to the 'else' statement. awt_ImagingLib.c 3307





Ну и последний интересный пример, связанный с возможной ошибкой copy paste:



Node* GraphKit::record_profiled_receiver_for_speculation(Node* n)
{
....
ciKlass* exact_kls = profile_has_unique_klass();
bool maybe_null = true;
if (java_bc() == Bytecodes::_checkcast ||
java_bc() == Bytecodes::_instanceof ||
java_bc() == Bytecodes::_aastore) {
ciProfileData* data =
method()->method_data()->bci_to_data(bci());
bool maybe_null = data == NULL ? true : <==
data->as_BitData()->null_seen();
}
return record_profile_for_speculation(n,
exact_kls, maybe_null);
return n;
}


Предупреждение PVS-Studio: V561 It's probably better to assign value to 'maybe_null' variable than to declare it anew. Previous declaration: graphKit.cpp, line 2170. graphKit.cpp 2175



Что же происходит в этом коде? Перед блоком if объявлена переменная bool maybe_null = true;. Затем, когда исполняется код в блоке if, объявляется переменная с аналогичным именем. После выхода из блока значение этой переменной будет утеряно, и вызов функции, использующей эту переменную, произойдет в любом случае со значением true. Хорошо, если эта переменная была продублирована с целью отладки. В противном случае данный код исполняется некорректно и требует модификации:

maybe_null = data == NULL ? true :    
data->as_BitData()->null_seen();

Работа с указателями







Работа с указателями требует внимательности и аккуратности, так как при неправильном использовании указателей могут появиться ошибки, которые будет сложно идентифицировать. Как правило, основную опасность представляет использование битых указателей или использование указателей без проверки на нулевое значение.



Первым делом рассмотрим случай явного использования нулевого указателя:

static jint JNICALL
cbObjectTagInstance(....)
{
ClassInstancesData *data;

/* Check data structure */
data = (ClassInstancesData*)user_data;
if (data == NULL) {
data->error = AGENT_ERROR_ILLEGAL_ARGUMENT;
return JVMTI_VISIT_ABORT;
}
....
}


Предупреждение PVS-Studio: V522 Dereferencing of the null pointer 'data' might take place. util.c 2424



Совершенно непонятный код с использованием нулевого указателя, может привести к аварийному завершению программы. Возможно, эта ветка никогда не срабатывала, благодаря чему удалось избежать проблем при работе программы. В этом же файле нашлось еще три подобных места:

  • V522 Dereferencing of the null pointer 'data' might take place. util.c 2543

  • V522 Dereferencing of the null pointer 'data' might take place. util.c 2601

  • V522 Dereferencing of the null pointer 'data' might take place. util.c 2760





А вот в следующих случаях возможность использования нулевого указателя скрыта глубже. Это очень распространенная ситуация, подобные предупреждения встречаются практически во всех проверяемых проектах:

static jboolean
visibleClasses(PacketInputStream *in, PacketOutputStream *out)
{
....
else {
(void)outStream_writeInt(out, count);
for (i = 0; i < count; i++) {
jbyte tag;
jclass clazz;

clazz = classes[i]; <==
tag = referenceTypeTag(clazz);

(void)outStream_writeByte(out, tag);
(void)outStream_writeObjectRef(env, out, clazz);
}
}

if ( classes != NULL ) <==
jvmtiDeallocate(classes);
....
return JNI_TRUE;
}


Предупреждение PVS-Studio: V595 The 'classes' pointer was utilized before it was verified against nullptr. Check lines: 58, 66. ClassLoaderReferenceImpl.c 58



В нижнем блоке указатель проверяется на нулевое значение, значит, программист допускает возможность того, что значение указателя будет нулевым. Но в блоке выше указатель используется без проверки. Соответственно, если значение указателя будет нулевым, то такая проверка нам не поможет, и мы получим аварийное завершение программы. Чтобы исправить эту ошибку, надо проверить указатель выше обоих блоков.



Приведу еще один аналогичный пример:

int InstructForm::needs_base_oop_edge(FormDict &globals) const {
if( is_simple_chain_rule(globals) ) {
const char *src = _matrule->_rChild->_opType;
OperandForm *src_op = globals[src]->is_operand();
assert( src_op, "Not operand class of chain rule" );
return src_op->_matrule ?
src_op->_matrule->needs_base_oop_edge() : 0;
} // Else check instruction

return _matrule ? _matrule->needs_base_oop_edge() : 0;
}


Предупреждение PVS-Studio: V595 The '_matrule' pointer was utilized before it was verified against nullptr. Check lines: 3534, 3540. formssel.cpp 3534



Здесь проверка указателя осуществляется снизу в тернарном операторе — _matrule? _matrule->needs_base_oop_edge(): 0;. А выше происходит простое обращение к этому указателю — const char *src = _matrule->_rChild->_opType;. Рецепт исправления аналогичен: нужно проверить указатель до его использования. Таких мест нашлось довольно много, я приведу их списком:

  • V595 The '_pipeline' pointer was utilized before it was verified against nullptr. Check lines: 3265, 3274. output_c.cpp 3265

  • V595 The 'index_bound' pointer was utilized before it was verified against nullptr. Check lines: 790, 806. c1_RangeCheckElimination.cpp 790

  • V595 The 'g_type_init' pointer was utilized before it was verified against nullptr. Check lines: 94, 108. GioFileTypeDetector.c 94

  • V595 The 'classArray' pointer was utilized before it was verified against nullptr. Check lines: 1169, 1185. JPLISAgent.c 1169

  • V595 The 'q' pointer was utilized before it was verified against nullptr. Check lines: 594, 599. mpi.c 594

  • V595 The 'info.waiters' pointer was utilized before it was verified against nullptr. Check lines: 224, 228. ObjectReferenceImpl.c 224

  • V595 The 'methods' pointer was utilized before it was verified against nullptr. Check lines: 225, 229. ReferenceTypeImpl.c 225

  • V595 The 'fields' pointer was utilized before it was verified against nullptr. Check lines: 433, 437. ReferenceTypeImpl.c 433

  • V595 The 'nested' pointer was utilized before it was verified against nullptr. Check lines: 538, 540. ReferenceTypeImpl.c 538

  • V595 The 'interfaces' pointer was utilized before it was verified against nullptr. Check lines: 593, 595. ReferenceTypeImpl.c 593

  • V595 The 'buf' pointer was utilized before it was verified against nullptr. Check lines: 265, 266. ps_proc.c 265

  • V595 The 'monitors' pointer was utilized before it was verified against nullptr. Check lines: 382, 387. ThreadReferenceImpl.c 382

  • V595 The 'monitors' pointer was utilized before it was verified against nullptr. Check lines: 557, 560. ThreadReferenceImpl.c 557

  • V595 The 'signature' pointer was utilized before it was verified against nullptr. Check lines: 520, 526. debugInit.c 520

  • V595 The 'BlackPoint' pointer was utilized before it was verified against nullptr. Check lines: 192, 208. cmssamp.c 192

  • V595 The 'nativename' pointer was utilized before it was verified against nullptr. Check lines: 506, 511. awt_Font.c 506

  • V595 The 'pseq->seq' pointer was utilized before it was verified against nullptr. Check lines: 788, 791. cmsnamed.c 788

  • V595 The 'GammaTables' pointer was utilized before it was verified against nullptr. Check lines: 1430, 1434. cmsopt.c 1430





Иногда программисты наоборот проверяют указатели, но делают это неправильно:

FileBuff::FileBuff( BufferedFile *fptr, ArchDesc& archDesc) : 
_fp(fptr), _AD(archDesc) {
....
_bigbuf = new char[_bufferSize];
if( !_bigbuf ) {
file_error(SEMERR, 0, "Buffer allocation failed\n");
exit(1);
....
}


Предупреждение PVS-Studio: V668 There is no sense in testing the '_bigbuf' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. filebuff.cpp 47



В данном случае проверка указателя _bigbuf на нулевое значение после использования new бессмысленна. В случае если системе не удастся выделить память, то будет сгенерировано исключение и выполнение функции прекратится. Для исправления ошибки можно использовать несколько подходов. Cделать выделение памяти в блоке try catch, или использовать для выделения памяти конструкцию new(std::nothrow), которая не будет генерировать исключение в случае неудачи. Есть еще несколько таких неправильных проверок:

  • V668 There is no sense in testing the 'vspace' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. psParallelCompact.cpp 455

  • V668 There is no sense in testing the 'uPtr' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. jni.cpp 113





Последняя ошибка в работе с указателями допущена при явном приведении указателя одного типа к указателю другого типа:

mlib_status mlib_convMxNext_f32(...)
{
mlib_d64 dspace[1024], *dsa = dspace;
....
mlib_f32 *fsa;
....

if (3 * wid_e + m > 1024) {
dsa = mlib_malloc((3 * wid_e + m) * sizeof(mlib_d64));

if (dsa == NULL)
return MLIB_FAILURE;
}

fsa = (mlib_f32 *) dsa; <==
....
}


Предупреждение PVS-Studio: V615 An odd explicit conversion from 'double *' type to 'float *' type. mlib_ImageConvMxN_Fp.c 294



Указателю на float mlib_f32 *fsa пытаются присвоить указатель на double mlib_d64 dspace[1024], *dsa = dspace. Типы float и double имеют различный размер и подобное приведение типов, скорее всего, ошибочно. Несоответствие размеров приводимых типов приведет к тому, что указатель fsa будет указывать на некорректный для типа float формат числа.



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

  • V615 An odd explicit conversion from 'double *' type to 'float *' type. mlib_ImageLookUp_Bit.c 525

  • V615 An odd explicit conversion from 'double *' type to 'float *' type. mlib_ImageLookUp_Bit.c 526



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



Разные ошибки







Следующая ошибка, наверное, тоже результат неудачного копирования кода:

static bool
parse_bool (const char **pp, const char *end, unsigned int *pv)
{
....

/* CSS allows on/off as aliases 1/0. */
if (*pp - p == 2 || 0 == strncmp (p, "on", 2))
*pv = 1;
else if (*pp - p == 3 || 0 == strncmp (p, "off", 2))
*pv = 0;
else
return false;

return true;
}


Предупреждение PVS-Studio: V666 Consider inspecting third argument of the function 'strncmp'. It is possible that the value does not correspond with the length of a string which was passed with the second argument. hb-shape.cc 104



Здесь как раз тот случай, когда ошибка не влияет на работоспособность программы. Вместо сравнения трех символов, сравниваются только первые два, я не исключаю, что автор кода может и специально сделал такую проверку. Так как значение в буфере p может быть on или off, то достаточно сравнивать и первые два символа. Но для порядка, все-таки можно скорректировать код:

else if (*pp - p == 3 || 0 == strncmp (p, "off", 3))


Нашлось несколько мест, где возможны ошибки при реализации классов:

class ProductionState {
....
private:
// Disable public use of constructor, copy-ctor, ...
ProductionState( ) :
_production(cmpstr, hashstr, Form::arena)
{ assert( false, "NotImplemented"); };
ProductionState( const ProductionState & ) :
_production(cmpstr, hashstr, Form::arena)
{ assert( false, "NotImplemented"); }; // Deep-copy
};


Предупреждение PVS-Studio: V690 Copy constructor is declared as private in the 'ProductionState' class, but the default '=' operator will still be generated by compiler. It is dangerous to use such a class. dfa.cpp 76



В этом классе попытались запретить копирование, но забыли добавить в приватную область оператор копирования. Он будет сгенерирован по умолчанию и будет доступен для использования. Даже если сейчас в коде нигде не используется этот оператор, то нет гарантии, что в дальнейшем кто-то случайно его не вызовет. При вызове такого оператора произойдет почленное копирование для класса, который не должен копироваться. Это может привести к различным эффектам вплоть до аварийного завершения программы. В данном случае следует добавить в приватную область объявление оператора "=".



Есть еще два класса, где есть подобные проблемы, желательно скорректировать их таким образом, чтобы не нарушать "Закон Большой Двойки".

  • V690 The 'MemRegion' class implements a copy constructor, but lacks the '=' operator. It is dangerous to use such a class. memRegion.hpp 43

  • V690 Copy constructor is declared as private in the 'Label' class, but the default '=' operator will still be generated by compiler. It is dangerous to use such a class. assembler.hpp 73





Последняя ошибка похожа на простую опечатку:

bool os::start_debugging(char *buf, int buflen) {
int len = (int)strlen(buf);
char *p = &buf[len];
....
if (yes) {
// yes, user asked VM to launch debugger
jio_snprintf(buf, sizeof(buf), "gdb /proc/%d/exe %d",
os::current_process_id(), os::current_process_id());

os::fork_and_exec(buf);
yes = false;
}
return yes;
}


Предупреждение PVS-Studio: V579 The jio_snprintf function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the second argument. os_linux.cpp 6094



Программист хотел передать длину буфера, но не учел, что он не локально объявленный массив, а указатель приходящий в аргументе функции. В результате вычисления выражения sizeof(buf) мы получим не длину буфера, а размер указателя, который будет равен 4 или 8 байтам. Исправить ошибку легко, так выше длина буфера уже была получена: int len = (int)strlen(buf);. Правильный вариант будет выглядеть следующим образом:

jio_snprintf(buf, len ....

Заключение







Всегда интересно проверять проект, который использует множество людей и следит за его качеством. Нашлось достаточно большое количество ошибок, в этой статье описано только их некоторое количество, остальные требуют более глубокого исследования. Найденные ошибки еще раз подтверждают эффективность использования статического анализатора, так как он позволяет обнаружить ошибки, которые трудно обнаружить при просмотре простым глазом. Эффективнее всего использовать анализатор на постоянной основе, так как он сэкономит много времени, которое было бы потрачено на отладку программы при поиске багов. Напоминаю, что вы можете попробовать работу статического анализатора PVS-Studio на своем проекте, загрузив его пробную версию.





Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. OpenJDK check by PVS-Studio.



Прочитали статью и есть вопрос?
Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.


Original source: habrahabr.ru.

https://habrahabr.ru/post/303534/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
formou

Футболка sun java

Четверг, 18 Июня 2015 г. 07:00 (ссылка)

Футболка sun java.


Читать далее
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Обзор IntelliJ IDEA 2016.2 Public Preview

Четверг, 16 Июня 2016 г. 17:45 (ссылка)

Время летит! Всего месяц назад мы стартовали IntelliJ IDEA 2016.2 EAP, а уже сегодня рады представить вашему вниманию Public Preview. Предлагаем вам убедиться в том, что этот месяц не прошел даром, прочитав этот пост. Нетерпеливые могут его пропустить и пойти сразу качать превью, чтобы попробовать все самостоятельно. Для остальных предлагаю короткий рассказ об основных улучшениях.



image





Отладчик



Продолжая бороться за пространство и общее удобство, мы объединили вкладку Watches с вкладкой Variables.



image



Также теперь можно использовать многострочные выражения в настройке брейкпойнта в полях Condition и Evaluate and log, и в настройках Data Type Renderers в поле Use following expression.



image



Интеграция с Git и Mercurial



Вкладка Log для Git и Mercurial в очередной раз претерпела ряд изменений. Ее содержание теперь обновляется в фоновом режиме (и при загрузке проекта, и при изменениях в локальном репозитории). За счет этого открываться вкладка стала гораздо быстрее.



Кроме того, во время обновления и загрузки под панелью инструментов теперь отображается тонкая полоска прогресса.



image



Если в поле Filter указана строка поиска, во время прокрутки внизу также появляется индикатор загрузки.



Подробная информация о коммитах показывается теперь для нескольких выделенных коммитов.



И наконец, можно установить шорткат для быстрого перехода к полю Filter.



Для Git мы исправили важную проблему, с которой наверняка сталкиваются пользователи Windows и OS X: переименование файлов, где меняется только регистр символов.



Работа с патчами



Если вы скопируете патч в буфер обмена и переключитесь в IDE (или перетащите патч в окно IDE мышью), вам автоматически предложат применить этот патч.



Кроме того, применение патча значительно упростилось в тех случаях, когда он не содержит информации о ревизии, а оригинальный файл был перенесен, переименован или изменен. В простейших случаях IDE постарается самостоятельно определить оригинальный файл, в сложных — предложит вам его указать. Также, если содержание файла изменилось, IDE поможет объединить эти изменения с патчем.



image



И наконец, перед применением патч теперь можно просто сравнить с локальной копией (кнопка Show Diff) и при необходимости внести изменения в локальную версию вручную по ходу сравнения.



Редактор



Редактор теперь поддерживает шрифты с лигатурами — специальными символами, образованными путем соединения других символов. Включить опцию можно в Settings -> Editor -> Colors & Fonts -> Font (флажок Enable font ligatures). Убедитесь, что выбранный шрифт поддерживает лигатуры, например FiraCode, Hasklig, Monoid или PragmataPro.



image



Интерфейс



Внешний вид IDE теперь можно настроить под себя, выбрав любое изображение как фон редактора и окна IDE. За счет настроек прозрачности это прекрасно работает и с темной, и со светлой темами.



Изображение выбирается с помощью действия Set Background Image, которое доступно как из Find Action, так и из контекстного меню на файле изображения.



image



Также внешне изменились всплывающие нотификации. Теперь они отображаются в нижнем правом углу (ближе к окну инструментов Events), имеют более компактную форму и группируются по типу подсистемы.



image



Spring Framework



Поддержка Spring Framework продолжает совершенствоваться.



Мы добавили инспекцию, которая предлагает автоматически заменять инжектирование полей конструкторами. Поддержали опцию (добавленную в Spring 4.3), которая позволяет использовать параметризированные типы (generics) в качестве классификаторов (qualifiers). Поддержали пользовательские аннотации EventListener, определенные с помощью AliasFor.



image



Кроме того, готова основательная поддержка Spring Cache (абстракции, добавленной в Spring 3.0 и полностью обновленной в Spring 4.1). Поддержка включает навигацию, инспекции, подсветку синтаксиса, автодополнение и навигацию при редактировании SpEl, и многое другое.



image



Для Spring MVC внутри шаблонов Freemarker и Velocity появилось автодополнение и навигация для переменных, объявленных в контроллере.



image



Также мы поддержали изменения Spring Security 4.0 и добавили автодополнение и навигацию при редактировании SpEl и для аннотаций и внутри XML.



image



Плюс мы добавили подсветку и навигацию для сообщений от Spring в консоли.



JavaScript



Теперь IDE не только помогает в написании ES6 кода, но и предлагает конвертировать в него код более старых версий JavaScript. Новый intention сможет заменить нормальные анонимные функции на стрелочные функции (arrow functions) и короткие стрелочные функции (shorthand arrow functions).



image



В дополнение к постфикс-автодополнениям, реализованным ранее, мы добавили шаблоны .const и .let.



image



React



IDE научилась понимать свойства (props) компонентов, объявленные при помощи propTypes, и теперь предлагает автодополнение и навигацию.



image



Методы жизненного цикла компонентов (например, componentDidMount) больше не подсвечиваются как неиспользуемые.



Когда вы передаете компоненту обработчики событий (например, onClick, onChange), IDE вместо кавычек ставит фигурные скобки (как и положено).



К слову о кавычках, теперь в Settings -> Editor -> Code Style -> HTML -> Other -> Generated quote marks вы вообще можете указать, что использовать: двойные кавычки, одинарные или вообще ничего.



image



Наконец, IDE научилась понимать non-DOM аттрибуты, такие как key, ref и dangerouslySetInnerHTML.



AngularJS



Для AngularJS 2 мы добавили много полезных шаблонов (live templates). В Project Wizard -> Static Web появился раздел Angular CLI.



image



TypeScript



IDE предлагает умное автодополнение для enum-типов.



image



Npm, Gulp и Grunt



Любой скрипт npm, Gulp и Grunt может теперь автоматически запускаться IDE перед запуском Run configuration. Для этого этот скрипт необходимо добавить в разделе Before launch в диалоге Run configuration.



image



Работа с базами данных



По мере развития DataGrip, нового продукта JetBrains, инструменты для работы с SQL и базами данных внутри IntelliJ IDEA также продолжают улучшаться.



В диалоге настроек Data Source and Drivers появилось автодополнение для поля Database.



image



В окне инструментов Database появилась опция Auto-scroll from Editor. При открытии того или иного элемента базы данных в редакторе, этот элемент выделяется в окне Database.



image



Автодополнение SQL стало еще больше учитывать контекст. Например, если в контексте ожидается имя таблицы, IDE больше не предлагает функции.



image



Surround With предлагает передать текущее выражение в качестве параметра функции.



image



Редактор таблицы предлагает автодополнение при редактировании значения таблицы (на основе других значений этого столбца).



image



Размеры столбцов таблицы можно менять с помощью шорткатов Ctrl + Shift + Вправо/Влево (

https://habrahabr.ru/post/303466/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Создание приложения под Android или проект без названия

Пятница, 10 Июня 2016 г. 12:33 (ссылка)

Предыстория



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

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



С тех пор я решил, что всегда буду так делать и в ближайшем будущем начну читать на английском языке, как на своем родном! Но через некоторое время меня начало утомлять резать карточки и тратить на это время, к тому же времени мне всегда категорически не хватает, в этот момент меня посетила мысль, а почему бы не создать программу под ОС android(т.к. у меня телефон именно с этой системой), которая будет выполнять аналогичную функцию!



Именно с этого момента зародился проект, которому я не придумал название.



Цель



Целью данного проекта, изначально, являлось автоматизация добавления слов в картотеку и дальнейшая их эксплуатация, но позже меня посетило еще пару идей и было очень интересно развивать проект, но спустя недели две это приелось и мне стало скучно, а полностью его забрасывать, как все предыдущие, я не хотел и поэтому решил попробовать себя в качестве автора данной статьи и поделиться полученным опытом в области сетевого программирования и программирования под android. В данной статье я постараюсь, на сколько это возможно, последовательно и шаг за шагом описать всю процедуру создания данной программы.



Программа



Начнем с того, что я подробно опишу созданный мной продукт и рассмотрю функционал, затем, кого это заинтересует, сможет дальше прочитать, о сотворении данного чуда.



Описание



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



image



Как видно на изображении, особенного ничего в нем нет, но при нажатии на кнопку «Проверка слов» или «Слова», появляется, так сказать, субменю.



image



image



Как видно из изображений сверху таким же свойством обладают кнопки «В Алфавитном порядке», «В Обратном порядке» и «Рандом». Их все я пометил специальным символом «+», а при раскрытии символ заменяется на «-». При повторном нажатии на один из пунктов раскрытого меню, оно сворачивается в исходное положение.



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



Как я уже говорил, перед созданием проекта, меня посетило много идей и одна из них, это упрощение добавление слов в программу. Никому не секрет, что человек существо ленивое и не каждому придется по вкусу вручную создавать свою картотеку, тем более если нужно заполнить аж три поля(хотя транскрипцию заполнять не обязательно), в век развития интернета и передачи данных на большие расстояния за одно мгновенье.



image



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



Также в программе есть функции изменения и удаления слов. Здесь совсем все просто: после нажатия на нужный пункт меню перед нами открывается активити, где слова расположены в алфавитном порядке. Окна удаления и изменения слов практически ничем не отличаются, лишь только иконкой на кнопке со словом, поэтому для их создания применялся один xml-файл.



image



image



Если пользователь изменят слово, то для его изменения используется xml-файл добавления слов, только с другим функционалом, все три поля заполнены старыми значениями.



image



После ввода новых значений, при нажатии на соответствующую кнопку, слово изменяется и сохраняется на устройстве пользователя без внесения изменений в БД на сервере. Удаление слов/слова, сделано тоже очень просто, я даже не представляю себе, как можно усложнить данный процесс.



image



image



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



Теперь, когда я описал один из основных функционалов, а именно добавление/удаление/изменение слов, рассмотрим наиболее важный, прохождение теста по данным словам, а затем опишу альтернативный способ добавления слов.



Можно было заметить, на рисунке выше, что тест можно проходить в трех разных порядках: алфавитный, обратный и рандом. Тестируемым объектом может быть либо английский вариант слова, либо русский, либо транскрипция.



image



Как видно из рисунка, активно поле с английским вариантом слова, значит тестируется именно этот вариант. В зависимости от выбранного теста, всегда активно только одно поле ввода, остальные изменению не подлежат. Строка «Статус» указывает на то, верен ли введенный вариант пользователя, для того чтобы это узнать, необходимо нажать на кнопку «Проверка», так же показано сколько слов пройдено и их число, кнопку «Далее» подробно можно не рассматривать, но хотелось бы отметить, что можно было бы добавить кнопку возращения к предыдущему слову. Тест можно завершить дойдя до последнего слова или же пунктом меню.



image



После того как вы прошли или завершили тест, вашему вниманию предоставляется ваш результат.



image



Слова, которые были введены правильно отображаются зеленым цветом, остальные красным, далее вы можете либо перейти в главное меню или пройти тест заново.



Теперь рассмотрим альтернативный способ добавления слов, чтобы им воспользоваться, нужно перейти в пункт меню «Загрузить» и там можно будет просматривать слова, которые были добавлены на сервер, способом описанным выше.



image



На рисунке видно, что данное активити практически не отличается от активити изменения и удаления, за исключением того, что здесь находятся CheckBox'ы, вместо Button'ов.



Хотелось бы отметить, что слова с сервера поступают небольшими порциями, а именно по сто штук, после того как пользователь доходит до предпоследнего, подгружается еще сто и так далее, пока сервер не вернет последнее слово. Для того чтобы слово/слова можно было добавить к себе в картотеку, следует воспользоваться пунктами меню.



image



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



На изображениях выше, можно заметить, что у активити изменения/удаления/загрузки слов есть Edit — «Найти», т.к. он реализован одинаков везде, то рассмотрим только на примере окна Загрузки.



Не смотря на простоту реализации, поиск слов в этой программе является моей любимой разработкой(не считай подгрузки слов с сервера). Он является «живим», ищет, английские и русские слова, а также прост и удобен в применении.



image



image



image



image



Так же в программе имеется пункт меню «Просмотреть слова», где все слова выводятся в HTML-таблице.



image



Напоследок хотелось бы отметить, что весь функционал всех активити, продублирован в меню.



image



image



image



image



image



Реализация



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



Клиентская часть



Клиентскую часть я начну описывать по порядку, так что первым делом мы рассмотрим создание меню, если быть точнее — реализацию сворачивание/разворачивание субменю. Данный фрагмент очень прост и практически не требует объяснений, так что я опишу только логику кода.











Как видно из фрагмента кода xml-файла, изначально все пункты субменю скрыты, далее, при нажатии на соответствующую кнопку, они становятся видимыми, а другие пункты, которые были активны, сворачиваются, внизу представлена одна из функций, остальные практически ничем не отличаются.



public void Alphabet(View view) {
click_alphabet = !click_alphabet;
if(Btn_Null())
Btn_Gone();
btn_english = (Button) findViewById(R.id.english1);
btn_transcription = (Button) findViewById(R.id.transcription1);
btn_russian = (Button) findViewById(R.id.russian1);
if(click_alphabet) {
btn_english.setVisibility(View.VISIBLE);
btn_transcription.setVisibility(View.VISIBLE);
btn_russian.setVisibility(View.VISIBLE);
btn_alphabet.setText("В АЛФАВИТНОМ ПОРЯДКЕ -");
} else {
Btn_Gone();
btn_alphabet.setText("В АЛФАВИТНОМ ПОРЯДКЕ +");
}
}


Переменная click_alphabet — проверяет, было ли повторное нажатие на кнопку, если «да», то субменю, за который отвечает данный пункт, сворачивается.



Функция Btn_Null() — проверяет инициализицию всех кнопок.

Функция Btn_Gone() — сворачивает абсолютно все пункты/подпункты меню.



Теперь рассмотрим добавление слова в БД на сервер и на смартфон. Все слова, которые пользователь себе добавил, хранятся в классе TreeMap, ключом для данного класса является английское слово, а транскрипция и перевод хранятся в классе TransRus.



public class TransRus {
private String transcription;
private String russian;
boolean error;
TransRus() {
transcription = new String("");
russian = new String("");
error = false;
}
void getTranscription(String tr) {
transcription = tr;
}
void getRussian(String rs) {
russian = rs;
}
String setTranscription() {
return transcription;
}
String setRussian() {
return russian;
}
void getError(boolean t) {
error = t;
}
boolean setError() {
return error;
}
}


Класс TreeMap обернут в оболочку класса CollectionWords, также для него был написан компаратор, который игнорирует регистр букв.



class ComparatorNotRegister implements Comparator {  public int compare(String str1, String str2) {
return str1.compareToIgnoreCase(str2);
}
}
public class CollectionWords {
static TreeMap coll_words = null;
static final String file1 = "english";
static final String file2 = "transcription";
static final String file3 = "russian";
static void InitializationCollWords() {
if(coll_words != null)
return;
coll_words = new TreeMap(new ComparatorNotRegister());
}
static void PutCollWords(String english, String transcription, String russian) {
TransRus tr = new TransRus();
tr.getTranscription(transcription);
tr.getRussian(russian);
coll_words.put(english, tr);
}
static void ChangedWordEng(String old_english, String new_english, String transcription, String russian) {
TransRus temp = coll_words.get(old_english);
coll_words.remove(old_english);
PutCollWords(new_english, transcription, russian);
}
static void DeleteWords(String eng) {
coll_words.remove(eng);
}
static void WriteWords(AppCompatActivity t) {
try(
BufferedWriter eng = new BufferedWriter(new OutputStreamWriter(t.openFileOutput(file1, t.MODE_PRIVATE)));
BufferedWriter trans = new BufferedWriter(new OutputStreamWriter(t.openFileOutput(file2, t.MODE_PRIVATE)));
BufferedWriter rus = new BufferedWriter(new OutputStreamWriter(t.openFileOutput(file3, t.MODE_PRIVATE)))) {
for(Map.Entry me : CollectionWords.AllWords()) {
eng.write(me.getKey() + "\n");
trans.write(me.getValue().setTranscription() + "\n");
rus.write(me.getValue().setRussian() + "\n");
}
} catch (FileNotFoundException e) {
Log.d("MyLog", "WF: " + e);
} catch (IOException e) {
Log.d("MyLog", "WIOE: " + e);
} catch(NullPointerException e) {
Log.d("MyLog", "WN: " + e);
} catch (Exception e) {
Log.d("MyLog", "WE: " + e);
}
}
static void ReadWords(AppCompatActivity t) {
try(
BufferedReader eng = new BufferedReader(new InputStreamReader(t.openFileInput(file1)));
BufferedReader trans = new BufferedReader(new InputStreamReader(t.openFileInput(file2)));
BufferedReader rus = new BufferedReader(new InputStreamReader(t.openFileInput(file3)))) {
String str_eng;
String str_trans;
String str_rus;
while(((str_eng = eng.readLine()) != null) &&
((str_trans = trans.readLine()) != null) &&
((str_rus = rus.readLine()) != null)) {
CollectionWords.PutCollWords(str_eng, str_trans, str_rus);
}
Log.d("MyLog", "Hyi tam");
} catch (FileNotFoundException e) {
Log.d("MyLog", "RF: " + e);
} catch (IOException e) {
Log.d("MyLog", "RIO: " + e);
} catch(NullPointerException e) {
Log.d("MyLog", "RN: " + e);
}
}
static Set> AllWords() {
return coll_words.entrySet();
}
}


Данный класс не представляет ничего сложного, как можно заметить, все слова хранятся в текстовых файлах english, transcription и russian, для этого следовало бы использовать БД, но я не стал сильно заморачиваться, т.к. все и так работает. Функции WriteWords, ReadWords служат для сохранения слов, здесь следует отметить, только то, что, чтобы функция работала, ей нужно будет передать указатель this, из класса, который ее вызвал. Сразу бросается в глаза, тот факт, что все функции статические, это было сделано специально, чтобы не дублировать класс много раз, все остальное понятно и комментарии будут лишними. Теперь, когда дополнительные оболочки рассмотрены, можно описать функцию добавления.



public void AddWord(View view) {
CollectionWords.InitializationCollWords();
if(english_language.getText().length() == 0 || russian_language.getText().length() == 0) {
Toast toast = Toast.makeText(getApplicationContext(),
"Заполните обязательные поля!", Toast.LENGTH_SHORT);
toast.show();
return;
}
if(status != 0 && status == MainActivity.INT_CHG) {
CollectionWords.ChangedWordEng(old_english, english_language.getText().toString(),
transcription_language.getText().toString().length() == 0 ? "-" : transcription_language.getText().toString(),
russian_language.getText().toString());
Back(null);
}
CollectionWords.PutCollWords(english_language.getText().toString(),
(transcription_language.getText().toString().length() == 0 ? "-" : transcription_language.getText().toString()),
russian_language.getText().toString());
ClientAddWords caw = new ClientAddWords();
caw.ServerAddWords("1(!!)" + english_language.getText().toString() + "(!!)" +
(transcription_language.getText().toString().length() == 0 ? "-" : transcription_language.getText().toString())
+ "(!!)" +
russian_language.getText().toString());
english_language.setText("");
transcription_language.setText("");
russian_language.setText("");
}


Из фрагмента кода видно, что обязательными полями здесь являются enlish_language и russian_language, если они не заполнены, то добавления слова не произойдет, функция завершится и всплывет сообщение с подсказкой. Как я говорил ранее, для добавления и изменения слов, используется один xml-файл, а соответственно и класс тоже, поэтому здесь присутствует второй оператор if, который проверяет переменную status, хранящее значение о том, был ли осуществлен вход в данную функцию, с целью изменения слова.

Особый интерес представляет собой класс ClientAddWords, который служит для отправки слова на сервер. Т.к. на данный момент Android Studio требует, чтобы всё, что связанно с сетью происходило в отдельном потоке, я решил создать класс, который за это отвечает. В программе присутствует еще один, возвращающий слов с сервера.



Как видно из кода, сначала создается объект ClientAddWords, потом объект вызывает функцию, ей передается строка, разделенная символом «(!!)», этот символ отделяет код операции, предназначенный для сервера, в данном случае это «1», а также английское слово, транскрипцию и перевод, это служит для того, чтобы сервер мог правильно отделить данные друг от друга, как вы уже догадались именно эта строка и передается серверу.



Создание класса, для работы с сетью, очень удобно, ведь подключение клиента к серверу идет именно в тот момент, когда вызывается функция ServerAddWords, а после передачи, клиент отключается от серверного приложения, так что дополнительной нагрузки на сервер нет. Ниже приведен класс ClientAddWords.



public class ClientAddWords extends Thread {
String str_user;
void ServerAddWords(String str) {
str_user = str;
start();
}
public void run() {
Log.d("MyLog", "Run Client");
InetAddress addr = null;
try {
addr = InetAddress.getByName("192.168.1.208");
} catch (UnknownHostException e) {
Log.d("MyLog", "ServerAddWords ClientAddWords: " + e);
}
Client c;
try {
c = new Client(addr);
} catch (IOException e) {
Log.d("MyLog", "Socket failed: " + e);
return;
}
c.Out(str_user);
c.Close();
}
}


Как видно, в данной реализации кода, нет ничего сложного и он не требует дополнительных разъяснений, код класса Client, представлен ниже.



public class Client extends Thread {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
InetAddress addr;
public String In() throws IOException {
return in.readLine();
}
public void Out(String str) {
out.println(str);
}
public Client(InetAddress addr) throws IOException {
this.addr = addr;
Log.d("MyLog", "Making client");
try {
socket = new Socket(addr, 8080);
} catch (IOException e) {
Log.d("MyLog", "Socket failed: " + e);
throw e;
}
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
} catch (Exception e) {
Log.d("MyLog", "In/Out: " + e);
try {
socket.close();
}
catch (IOException e2) {
Log.d("MyLog", "Client: Socket not closed");
}
}
}
public void Close() {
try {
socket.close();
in.close();
out.close();
} catch (IOException e) {
Log.d("MyLog", "Close Client: " + e);
}
}
}


Здесь также нет интересных моментов, данный пример кода можно найти на любом сайте посвященный сетевому программированию.



Сейчас мы рассмотрим класс, отвечающий за изменение и удаление слов, как я уже не раз говорил они используют один xml-файл, и самое интересное в его реализации — это заполнение layout'a button'ами.









В самом xml-файле интерес представляет только этот кусок кода, здесь видно что layout'у задается id — ll_find, это нужно для того, чтобы создавать кнопки программно.



class MyButton extends Button {
private String str_eng;
private String str_trans;
private String str_rus;
private int index;
public MyButton(Context context) {
super(context);
}
void setEnglish(String eng) {
str_eng = eng;
}
void setTranscription(String trans) {
str_trans = trans;
}
void setRussian(String rus) {
str_rus = rus;
}
void setIndex(int id) { index = id; }
String getEnglish() { return str_eng; }
String getTranscription() {
return str_trans;
}
String getRussian() {
return str_rus;
}
int getIndex() { return index; }
}


Отмечу сразу данный класс, как не трудно догадаться он предназначен лишь для хранения значений карточки, а именно английское слов, его транскрипцию и перевод.



Первая функция, которую следует описать — это CreateButton, код представлен ниже.



public MyButton CreateButton(int i, String eng, String trans, String rus) {
final MyButton btnNew = new MyButton(this);
btnNew.setBackgroundResource(R.drawable.background_button);
btnNew.setText(eng + " - " + rus);
btnNew.setEnglish(eng);
btnNew.setTranscription(trans);
btnNew.setRussian(rus);
btnNew.setIndex(i);
if(status == MainActivity.INT_DEL) {
btnNew.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_delete_button, 0, 0, 0);
btnNew.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder ad;
Find context;
context = Find.this;
String title = "Удаление слова";
String message = "Вы уверены?";
String button1String = "Да";
String button2String = "Нет";
ad = new AlertDialog.Builder((Context) context);
ad.setTitle(title);
ad.setMessage(message);
ad.setPositiveButton(button1String, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int arg1) {
CollectionWords.DeleteWords(btnNew.getEnglish());
ll_layout.removeView(btnNew);
}
});
ad.setNegativeButton(button2String, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int arg1) {
}
});
ad.show();
}
});
}
else {
btnNew.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon_changed_button, 0, 0, 0);
btnNew.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("MyLog", "Changed Words");
ChangedWord((MyButton) v);
ll_layout.removeView((MyButton) v);
}
});
}
return btnNew;
}


Здесь есть пару интересных моментов, а именно изменение заднего фона кнопки, далее, в зависимости от того, с какой целью посещается данный класс, это создание иконки у кнопки, за это отвечает функция setCompoundDrawablesWithIntrinsicBounds, а также реализация события setOnClickListener, тут либо создается диалоговое окно и происходит удаление слова и btnNew, либо вызывается функция ChangedWord, которая открывает активити, предназначенное для изменения слова.



Следующая функция, про которую следует рассказать, это ShowViewWords, ее предназначение занесение button'ов в layout.



public void ShowViewWords(LinearLayout.LayoutParams lParams, String sub_str) {
int i = 0;
String first_chr = new String("");
for(Map.Entry me : CollectionWords.AllWords()) {
if(sub_str.length() != 0 && (me.getKey().toLowerCase().indexOf(sub_str.toLowerCase()) == -1 &&
me.getValue().setRussian().toLowerCase().indexOf(sub_str.toLowerCase()) == -1))
continue;
if(!first_chr.equals(String.valueOf(me.getKey().toUpperCase().charAt(0)))) {
first_chr = String.valueOf(me.getKey().toUpperCase().charAt(0));
TextView temp = new TextView(this);
temp.setText(first_chr + ":");
temp.setTextSize(25f);
ll_layout.addView(temp, i, lParams);
i++;
}
ll_layout.addView(CreateButton(i, me.getKey(), me.getValue().setTranscription(), me.getValue().setRussian()),
i, lParams);
i++;
}
if(i == 0) {
LinearLayout.LayoutParams l = CreateParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL);
TextView not_found = new TextView(this);
not_found.setText("Ничего не найдено");
ll_layout.addView(not_found, l);
}
}


Здесь также происходит создание и добавление TextView'а, который хранит значение первой буквы слова, отличающегося от первой буквы следующего слова. Постарался объяснить как можно лучше, но на всякий случай в пример приведу изображение.



image



За это отвечает вот этот кусок кода.



if(!first_chr.equals(String.valueOf(me.getKey().toUpperCase().charAt(0)))) {
first_chr = String.valueOf(me.getKey().toUpperCase().charAt(0));
TextView temp = new TextView(this);
temp.setText(first_chr + ":");
temp.setTextSize(25f);
ll_layout.addView(temp, i, lParams);
i++;
}


Изначально переменной first_chr, присваивается пустая строка, так что если цикл проходит первый раз, то в нее обязательно занесется значение.



За условие цикла отвечает данная строка.



for(Map.Entry me : CollectionWords.AllWords()) 


О предназначении первого оператора if, я расскажу позже, т. к. это относится к поиску, а о поиске расскажу в последнюю очередь.



 ll_layout.addView(CreateButton(i, me.getKey(), me.getValue().setTranscription(), me.getValue().setRussian()),
i, lParams);


А за добавление в ll_layout, отвечает это конструкция, здесь вызывается функция CreateButton, рассмотренная выше и возвращающая значение MyButton, далее добавляется индекс и параметры этой кнопки.



Функция ShowViewWords, вызывается из функции CreatesButton, а функция CreatesButton вызывается уже из onCreate. Код CreatesButton приведен ниже.



public void CreatesButton(String str_find) {
ll_layout.removeAllViews();
LinearLayout.LayoutParams lParams = CreateParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
Gravity.LEFT);
lParams.topMargin = 1;
ShowViewWords(lParams, str_find);
}


Код функции CreateParams:



public LinearLayout.LayoutParams CreateParams(int width, int height, int gravity) {
LinearLayout.LayoutParams lParams = new LinearLayout.LayoutParams(width, height);
lParams.gravity = gravity;
return lParams;
}


На данном этапе описании программы, я подошел к реализации класса TestWords, который отвечает за тестирование слов. Как и обычно в нем нет ничего сложного, но есть пару моментов, которые следует рассмотреть. Самый первый, это выбор пользователем теста, как ни трудно догадаться всего может быть девять вариантов тестирования, а именно заданный порядок слов(в алфавитном, в обратном и рандом) и объект тестирования(английски, транскрипция, русский). Так что самой первой вызывается функция StatusTest. Она определяет по коду, какие из Edit'ов должны быть заблокированы, и инициализирует переменную индексом, под которым стоит слово.



public void StatusTest() {
switch (status) {
case MainActivity.INT_ALPH_ENG:
edit_transcription.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
next_words = 0;
break;
case MainActivity.INT_ALPH_TRANS:
edit_english.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
next_words = 0;
break;
case MainActivity.INT_ALPH_RUS:
edit_english.setRawInputType(0x00000000);
edit_transcription.setRawInputType(0x00000000);
next_words = 0;
break;
case MainActivity.INT_REVS_ENG:
edit_transcription.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
next_words = CollectionWords.coll_words.size() - 1;
break;
case MainActivity.INT_REVS_TRANS:
edit_english.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
next_words = CollectionWords.coll_words.size() - 1;
break;
case MainActivity.INT_REVS_RUS:
edit_english.setRawInputType(0x00000000);
edit_transcription.setRawInputType(0x00000000);
next_words = CollectionWords.coll_words.size() - 1;
break;
case MainActivity.INT_RAND_ENG:
rand_next_words = new Random();
next_words = MethodRandomWords();
edit_transcription.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
break;
case MainActivity.INT_RAND_TRANS:
rand_next_words = new Random();
next_words = MethodRandomWords();
edit_english.setRawInputType(0x00000000);
edit_russian.setRawInputType(0x00000000);
break;
case MainActivity.INT_RAND_RUS:
rand_next_words = new Random();
next_words = MethodRandomWords();
edit_english.setRawInputType(0x00000000);
edit_transcription.setRawInputType(0x00000000);
break;
}
}


В случае алфавитного порядка переменной next_words, присваивается 0, обратный порядок — это размер класса TreeMap минус 1 и рандомный порядок, специальная функция, которая возвращает случайное значение в next_words.



Функция MethodRandomWords служит для того, чтобы не вернуть одно и тоже случайное значение несколько раз.



После инициализации всех Edit'ов и вызова функции StatusTest, выполняется метод ReadWord.



public void ReadWord() {
last_words++;
amount_words.setText(last_words + "/" + CollectionWords.coll_words.size());
switch (status) {
case MainActivity.INT_ALPH_ENG:
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_ALPH_TRANS:
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_ALPH_RUS:
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
case MainActivity.INT_REVS_ENG:
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_REVS_TRANS:
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_REVS_RUS:
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
case MainActivity.INT_RAND_ENG:
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_RAND_TRANS:
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_RAND_RUS:
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
}
status_true_word.setText("Статус:-");
}


Цель этого метода, загрузить первое слов, за это отвечает функция VecNextWord, параметр которой индекс и возвращаемым значением является Map.Entry, а также указать количество слов и обнулить статус.



Следующую роль играют всего две функции, Check — проверяет правильность введенного слова и при случае меняет Статус, NextWord — возвращает следующее по списку слово, в зависимости от выбранного порядка. Код функций приведен ниже.



public void Check(View view) {
check_bool = true;
Log.d("MyLog", "Status: " + status);
switch (status) {
case MainActivity.INT_ALPH_ENG:
Log.d("MyLog", "Check()");
if(edit_english.getText().toString().equals(nw.getKey())) {
Log.d("MyLog", "True");
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
Log.d("MyLog", "False");
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_ALPH_TRANS:
Log.d("MyLog", "Check()");
if(edit_transcription.getText().toString().equals(nw.getValue().setTranscription())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_ALPH_RUS:
Log.d("MyLog", "Check()");
if(edit_russian.getText().toString().equals(nw.getValue().setRussian())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_REVS_ENG:
Log.d("MyLog", "Check()");
if(edit_english.getText().toString().equals(nw.getKey())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_REVS_TRANS:
Log.d("MyLog", "Check()");
if(edit_transcription.getText().toString().equals(nw.getValue().setTranscription())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_REVS_RUS:
Log.d("MyLog", "Check()");
if(edit_russian.getText().toString().equals(nw.getValue().setRussian())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_RAND_ENG:
Log.d("MyLog", "Check()");
if(edit_english.getText().toString().equals(nw.getKey())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_RAND_TRANS:
Log.d("MyLog", "Check()");
if(edit_transcription.getText().toString().equals(nw.getValue().setTranscription())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
case MainActivity.INT_RAND_RUS:
Log.d("MyLog", "Check()");
if(edit_russian.getText().toString().equals(nw.getValue().setRussian())) {
status_true_word.setText("Статус: Верно");
Log.d("MyLog", "next_words: " + next_words);
nw.getValue().getError(true);
} else {
status_true_word.setText("Статус: Не верно");
}
break;
}
}

public void NextWord(View view) throws InterruptedException {
if(last_words >= CollectionWords.coll_words.size()) {
ResultTestGo();
return;
}
if(!check_bool && last_words != 0)
Check(view);
AddWordInTable(nw.getValue().setError());
last_words++;
amount_words.setText(last_words + "/" + CollectionWords.coll_words.size());
switch (status) {
case MainActivity.INT_ALPH_ENG:
next_words++;
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_ALPH_TRANS:
next_words++;
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_ALPH_RUS:
next_words++;
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
case MainActivity.INT_REVS_ENG:
next_words--;
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_REVS_TRANS:
next_words--;
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_REVS_RUS:
next_words--;
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
case MainActivity.INT_RAND_ENG:
next_words = MethodRandomWords();
nw = VecNextWord(next_words);
edit_english.setText("");
edit_transcription.setText(nw.getValue().setTranscription());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_RAND_TRANS:
next_words = MethodRandomWords();
nw = VecNextWord(next_words);
edit_transcription.setText("");
edit_english.setText(nw.getKey());
edit_russian.setText(nw.getValue().setRussian());
break;
case MainActivity.INT_RAND_RUS:
next_words = MethodRandomWords();
nw = VecNextWord(next_words);
edit_russian.setText("");
edit_english.setText(nw.getKey());
edit_transcription.setText(nw.getValue().setTranscription());
break;
}
status_true_word.setText("Статус:-");
check_bool = false;
}


Я не думаю, что у кого-то могут возникнуть трудности с кодом этих функция, но необходимо отметить пару моментов. Как было показано на изображении выше, после прохождения или завершения теста, формируется Html-таблица, для удобного отображения результата. Исход код таблицы, для быстродействия программы, формируется во время прохождения теста, при помощи функции AddWordInTable(nw.getValue().setError()), ей передается булево значение.



public void AddWordInTable(boolean temp) {
table_result += "
" + nw.getKey() +
"
" + nw.getValue().setTranscription() +
"
" + nw.getValue().setRussian() +
"
" + (temp ? "Верно" : "Не верно") + "
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<java - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda