Основы грамотности Java на конкретных примерах. Часть I

от
Совершенный код

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

Начинаем исправлять, двигаясь сверху вниз.

1. Бессмысленно использовать в данном конкретном случае полновесный объект Boolean, можно обойтись примитивным типом boolean, к тому же это позволит немного сэкономить память. Поэтому исправляем:
  1. boolean up;
  2. boolean down;
Также, переменная str не несет никакой полезной нагрузки, поэтому от нее можно избавиться.

2. Конструктор. При наследовании от Canvas не требуется специально вызывать конструктор суперкласса, поэтому смело можно удалять super() из конструктора. Также, как вы помните, мы избавились от переменной str, нужно вычистить ее из конструктора. Удалим строку str = s; , а строку p = parsing.splitString(str,"\n"); заменим на p = parsing.splitString(s,"\n");

3. Строка Thread thread = new Thread(this); избыточна, хотя и возможна. Однако красивее объединить ее с thread.start(); в конструкцию
  1. new Thread(this).start();
чтобы не создавать лишних явных локальных объектов в конструкторе.

4. Метод run(). Строки
  1. if (down == false) down = false;
  2. if (up == false) up = false;
являются полной тавтологией, которую нужно удалить. Вообще, сравнение в стиле if (ololo == true) всегда лучше сократить до if (ololo). Кроме того, задержка в 5 мс вместо 20 вполне достаточна, а в catch следует указать точный вид возможного исключения - InterruptedException. В итоге метод стал выглядеть так:
  1. public void run(){
  2.  
  3. while(true){
  4.  
  5.  
  6. if (up) up();
  7. if (down) down();
  8.  
  9. try{
  10.  
  11. Thread.sleep(5);
  12.  
  13. } catch (InterruptedException e){}
  14.  
  15. }
  16.  
  17. }

5. Переходим в метод paint(Graphics g). Первое, что бросается в глаза - строка g.setFont(Font.getFont(0, 0, Font.SIZE_SMALL)); ведь у нас уже есть заранее заготовленный в конструкторе объект font. Поэтому заменяем параметр на него:
  1. g.setFont(font);
Далее следует цикл
  1. for (int i=0; i< p.length; i++){
  2.  
  3. String in = Integer.toString(i);
  4.  
  5. g.setColor(0,0,0);
  6. g.drawString(in + " "+p[i], 0, id + i* fh,20);
  7.  
  8. }
займемся его оптимизацией.
Первое, что приходит в голову - вынести объявление переменной in за цикл. Однако, от него можно и вообще избавиться, об этом чуть ниже.
Второе, так как в процессе вывода строк их цвет не меняется динамически, то g.setColor(0,0,0); также можно вынести за пределы цикла.
третье, так как строки выводятся в цикле, то лучше заменить банальное сложение на конкатенацию, кроме того, последний параметр (якорь) не нужен, так как все координаты задаются явно, а так же избавимся от ненужной локальной переменной, то есть приводим строку
  1. g.drawString(in + " "+p[i], 0, id + i* fh,20);
к виду
  1. g.drawString(Integer.toString(i).concat(" ").concat(p[i]), 0, id + i * fh, 0);

Итоговый вид метода:
  1. public void paint(Graphics g){
  2. g.setFont(font);
  3. g.setColor(255,255,255);
  4. g.fillRect(0,0,width,height);
  5.  
  6. g.setColor(0,0,0);
  7.  
  8. for (int i=0; i< p.length; i++) g.drawString(Integer.toString(i).concat(" ").concat(p[i]), 0, id + i * fh, 0);
  9.  
  10. repaint();
  11. }

6. Метод keyPressed(int key). Константы вида Canvas.KEY_NUM2 избыточны, так как наш класс уже является наследником Canvas, поэтому имя суперкласса можно опустить:
  1. public void keyPressed(int key){
  2.  
  3. if (key == KEY_NUM2) up= true;
  4.  
  5. if (key == KEY_NUM8) down = true;
  6.  
  7. }
к тому же в таком варианте возможна ситуация, когда будут зажаты одновременно и 2 и 8, и текст просто застынет. Чтобы это исключить, следует использовать конструкцию switch вместо условий:
  1. public void keyPressed(int key){
  2.  
  3. switch (key) {
  4. case KEY_NUM2:
  5.   up = true;
  6.   break;
  7. case KEY_NUM8:
  8.   down = true;
  9.   break;
  10. }
  11.  
  12. }
операторы break автоматически не дают выполнится неверным условиям.

7. keyRealised :-D такого метода в Canvas нет вообще, есть keyReleased. Имеет смысл сделать его по аналогии с предыдущим методом, то есть:
  1. public void keyReleased(int key){
  2.  
  3. switch (key) {
  4. case KEY_NUM2:
  5.   up = false;
  6.   break;
  7. case KEY_NUM8:
  8.   down = false;
  9.   break;
  10. }
  11.  
  12. }

8. Метод down() также можно немного оптимизировать. В первую очередь мы видим, что довольно часто используется конструкция fh * p.length. Можно рассчитать это значение вначале метода, также для красоты кода можно заменить строчку id = id - fh; на id -= fh; :
  1. public void down(){
  2.  
  3. int tmp_height = fh * p.length;
  4. if (tmp_height > height){
  5.  
  6. id -= fh;
  7.  
  8. if (height - id> tmp_height) id = height - tmp_height;
  9.  
  10.  
  11. }
  12. }

9. Метод up() в целом нормальный, но также можно заменить строку id = id + fh; на id += fh;

10. Комментарий /* end */ банален, его можно удалить.

Итого, получаем следующий код:
Открыть спойлер

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

Итак, основную идею я показал, в продолжении мы поговорим о логике программы и некоторых трюках, обеспечивающих с одной стороны эффективность, а с другой - красоту кода.
  • +11
  • views 6623