Архив метки: Java

Для каждого языка программирования свои задачи, или выводы «9 лет спустя»

Окидывая взглядом последние 9 лет практики программирования, я пришел к ряду интересных и, думаю, полезных выводов. Свою историю изучения языков программирования (далее «ЯП») проиллюстрировал ниже. Где-то отметил фреймворки на этих ЯП. Отмеченные фреймворки, как правило, давали новую мотивацию и открывали новые горизонты в изучении и использовании этих ЯП.

Языки программирования по жизни

Языки программирования по жизни

Вывод 1: для разных классов задач – разные языки программирования

В действительности, достаточно хорошо изучив какой-либо язык, кажется, что на нём надо писать не только профильные приложения. Но будьте осторожны! На этом пути вы можете впасть в состояние «вбивания гвоздей микроскопом». Так рождаются демоны (постоянно висящие процессы, типа серверов) на PHP, веб-сервера на JavaScript, веб-приложения на C++ и прочее. В качестве эксперимента – это прекрасно, но всё становится гораздо ужасней, когда что-то в силу своей популярности в другой области становится частым решением в других.

Хорошим примером иллюстрирующим первый вывод является node.js и Erlang. На node.js с радостью накинулось большое число веб-разработчиков, ведь все знают JavaScript! Попробовав домашние проекты на этой платформе, многие начали использовать node.js в реальных проектах и в итоге столкнулись с большими проблемами, решение которых затрачивало всё больше сил. Например, отказоустойчивость таких приложений. Стоит вылезть ошибке в одном из потоков выполнения, и весь сервер падает, если только вы не оградились везде, где только можно try-catch блоками. Или горизонтальное масштабирование. Тут вообще беда. И рождаются костыли-костыли-костыли.

Другое дело Erlang, который изначально «by-design» создавался для больших телекоммуникационных систем. Писать на нём серверное приложение — сплошное удовольствие. Хотите постоянно работающий сервер, которым можно управлять по telnet? Да легко! Держите встроенный пакет gen_tcp. Горизонтальное масштабирование существующего приложения? Вам скорей всего придется поменять пару строчек, где описывается передача сообщений другим функциям, — добавить в них отправку сообщений на другие ноды. Тоже и с отказоустойчивостью (привет супервизорам), обработкой бинарных данных и пр. пр.

Вывод 2 – не упускайте возможности изучать новые ЯП и/или их популярные фреймворки

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

Существует две основные когорты ЯП:

  1. Си-подобные: С++, С#, Java, PHP, JavaScript, ActionScript и др.
  2. LISP-подобные: Erlang, Python, Ruby, Perl и др.

Если вы знаете хотя бы один язык из каждого класса, то считайте, что вы знаете и все остальные ЯП.) Исключениями можно считать Assembler и языки-приколы, типа Brainfuck. Кстати, часто различить эти классы ЯП можно по наличию/отсутствию кортежей и обрамлению условных операций и циклов в фигурные скобки.

Я не стал разбивать на группы ООП, функциональные и процедурные парадигмы программирования, т.к. по большому счету почти любой подход можно использовать почти в любом языке (например, функциональный подход в Java). Другое дело, что язык изначально для этого не создавался (см. вывод 1;-) и такие извороты выглядят крайне противоестественно. Исключение Python. Он изначально разрабатывался мульти-парадигмальным.

Мой топ-рейтинг открытий типа: «А-а-а! Так вот с помощью чего это надо было делать!»:

1.  Zend Framework (на PHP)

Ну нафига я писал свою глючную библиотеку для работы с БД? О боги, а зачем я каждый раз придумывал новую архитектуру для каждого нового веб-приложения? MVC, Zend_Db, Zend_Action даруют возможность писать веб-приложения быстрее, а код делать надежным!

2.  Erlang

Боже! Ну почему я потратил кучу времени на написание этого простого демона на Java, если в Эрланге это можно было сделать парой десятков строчек, да и работало бы безотказно, да и масштабировать было бы проще!

3.  Octave

Так, надо бы быстрое преобразование Фурье… О, Matlab! Ну что, пошли на страшное преступление в ближайший торрент-трекер. Хотя ладно, вот есть бесплатная библиотечка для любимого PHP. Эхх…что-то как-то всё грустно и медленно. Octave? Octave… Octave! Мега-удобный синтаксис работы с матрицами, встроенные функции для кучи математических задач, да тут еще и графики с пол пинка строятся! Да оно еще совместимо с MatLab! А еще open-source!

4.  KnockoutJS (на JavaScript).

Как же мне надоели эти простыни с кодом, запутанная логика работы с объектами на странице, эти бесконечные onSomeEvent функции. Как бы хотелось MVC, но вот только на клиенте. Чтобы вот есть у тебя модель данных и при её изменении автоматически всё само менялось на странице. Добавил к ней объект «Задача», а на странице возьми и появись соответствующий блок для новой задачи.

KnockoutJS, как ты вовремя!

5.  Django (на Python)

Да-да, Zend Framework, ты не плох, но ты… рутинный. Каждый раз надо писать свою админку, прикручивать авторизацию, свои однотипные операции добавления, редактирования и удаления сущностей, расписывать формы ввода данных, писать однотипные виды для типичных задач, типа показа по 10 новостей на одной странице и разбивках остальных новостей по страницам. Copy-paste, copy-paste…

Ммм… что-то зачастили на хабре со своими дифирамбами во славу Django и Python.  Да давно уже поют, который год. Что ж они всё никак не угомонятся?) Ну попробуем и мы.

Так-так… значит я раскомментирую вот тут и вот тут, еще одна команда в консоли и… у меня готова полноценная админка?! Вы издеваетесь?) Так просто! А вот еще бы хотелось, чтобы в админке было удобно приписывать к разным авторам разные статьи, чтобы было красивая связь многим-ко-многим. Добавить одну строку в модель?! Ну-у, у вас совсем нет совести! Зачем же я столько времени тратил на эту рутину в зенде?»

Для этой записи хватит выводов, а то, ведь, до конца не дочитаете!) Оставим еще парочку на будущее.)

Java: собираем проект с Maven для совместной работы над проектом

Предыстория: есть java библиотека (в моем случае библиотека ektorp для работы с CouchDB)  которая имеет кучу зависимостей (то есть использует исходные коды других библиотек). Заботливый автор вместо упаковки всех необходимых библиотек (зависимостей) вместе с оригинальной библиотекой выложил свою библиотеку в общее хранилище (репозитарий) хороших библиотек Maven.

Задача: использовать библиотеку в совместном проекте. Над проектом будут работать другие люди, которые ничего не знаю о Maven и обучение этой технологии займет достаточное время. Поэтому принято решение собрать в один jar файл все зависимости библиотеки вместе с самой библиотекой.

Решение:

  1. В eclipse устанавливаем плагин m2eclipse (Maven 2 for eclipse).
  2. Учимся как им пользоваться на сайте. Все представлено в наборе видюшек, очень удобно и понятно.
  3. Создаем maven проект (в Project explorer -> Create Maven Project). Назовем его ektrop-assembled (group id, artifact id).
  4. В зависимостях добавляем org.ektorp
  5. Настраиваем pom.xml. Здесь мы добавляем общедоступный плагин для сборки проекта (maven-assembly-plugin). Настраиваем его на сборку проекта в один файл и чтобы при запуске этапа package этот плагин делал свою работу. Конечное содержимое файла pom.xml:
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>ektorp-assembled</groupId>
      <artifactId>ektorp-assembled</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <build>
      	<plugins>
      		<plugin>
      			<groupId>org.apache.maven.plugins</groupId>
      			<artifactId>maven-assembly-plugin</artifactId>
      			<version>2.2.1</version>
      			<configuration>
    	          <descriptorRefs>
    	            <descriptorRef>jar-with-dependencies</descriptorRef>
    	          </descriptorRefs>
    	        </configuration>
    	        <executions>
    	          <execution>
    	            <id>make-assembly</id> <!-- this is used for inheritance merges -->
    	            <phase>package</phase> <!-- bind to the packaging phase -->
    	            <goals>
    	              <goal>single</goal>
    	            </goals>
    	          </execution>
    	        </executions>
      		</plugin>
      	</plugins>
      </build>
      <dependencies>
        <dependency>
          <groupId>org.ektorp</groupId>
          <artifactId>org.ektorp</artifactId>
          <version>1.1.1</version>
          <scope>compile</scope>
        </dependency>
      </dependencies>
    </project>
    
  6. В eclipse выбираем Run as..->Maven package
  7. После всех процедур в папке проекта target появляется файл ektrop-assembled-0.0.1-SNAPSHOT-jar-with-dependencies.jar – это и есть необходимый файл с основной библиотекой и всеми её зависимостями.
  8. Полученный jar копируем в любой проект и подключаем в Build path.

Java: бинарный поиск

Цель: научится писать красивый и эффективный код.

Задача: перед использованием какого-либо алгоритма, реализация которого есть в библиотеках, написать самому его реализацию.

Решение:

недавно на хабре был замечательный пост про то, что в действительности даже простые алгоритмы типа дихотомии (бинарный поиск) могут написать только 10% программистов. В действительности этот пост оказался проверкой своих качеств как программиста для тех, кто его читал. И правда, многие в комментариях честно признавались, что написание этого алгоритма заняло у них от 30 до 90 минут. Решил и я попробовать, и спустя 40 минут нарисовалось следующее:

	public static int binarySearch(int numb, int[] array) {
		int border = (int) Math.floor(array.length/2);
		int current = border;
		int tmp = array[current];
		do {
			if (tmp == numb) {
				return current;
			}
			if (numb == array[current - border]) {
				return current-border;
			}
			
			if (numb == array[current + border - 1]) { // Тут внесена правка, см. комментарии
				return current+border;
			}

			border = (int) Math.floor(border/2);
			if (tmp < numb) {
				tmp = array[current + border];
				current += border;
			} else {
		        tmp = array[current - border];
		        current -= border;
			}
		} while (border != 0);
		return -1;
	}

Что, конечно, было довольно громоздко и не слишком быстро (и довольно грязно), но работало. Однако самое интересное произошло, когда я нашел исходные коды реализации этого алгоритма в стандартной библиотеке java.util.arrays:

    // Like public version, but without range checks.
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
				     int key) {
	int low = fromIndex;
	int high = toIndex - 1;

	while (low <= high) {
	    int mid = (low + high) >>> 1;
	    int midVal = a[mid];

	    if (midVal < key)
		low = mid + 1;
	    else if (midVal > key)
		high = mid - 1;
	    else
		return mid; // key found
	}
	return -(low + 1);  // key not found.
    }

Исходники находятся в архиве src.zip, который лежит в JDK (у меня это было в C:\Program Files\Java\jdk1.6.0_18).
Самое интересно (и после ставшее очевидным), что целочисленное деление на два можно выполнять простым поразрядным беззнаковым оператором сдвига вправо a >>> n, который смещает биты в числе a вправо на n разрядов. Более того, как совершенно верно заметил один пользователь в ЖЖ:

В оригинальной реализации даже не нужна проверка на переполнение, потому что, даже если происходит переполнение, потом мы сдвигаем сумму вправо на 1 бит, последний бит заполняя ноликом (потому что >>>) — а не просто деля получившуюся (возможно, отрицательную в результате переполнения) сумму пополам, таким образом, даже если числа были по размеру как близкие к максимуму int’ы, всё равно число посередине будет найдено корректно.

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

Ссылки в записи

  • Запись на habrahabr.ru о том, что по исследованиям Дональда Кнута, только 10% программистов могут написать реализацию двоичного поиска