В итоге вечерами дома в свободное от работы время я покрыл за 1 неделю следующие направления: Sleep/Delay/Pause, Timer for Benchmark, Random range generator, File operations, Tasks/Threads, Reflection, JSON, URL, Logging, Strings. Смотрел свои проекты на предмет копи-паста стандартных решений и писал решения в библиотеку. В очередной раз применил TDD подход для разработки библиотеки. Сначала пишешь тест на не существующие классы и методы, а потом реализуешь код, что бы тесты стали зелеными. Решает две проблемы: во-первых, ты пытаешься удобно использовать свои классы и их методы до их реализации, во-вторых, у тебя остаются тесты, которые в будущем могут свалиться, и ты поймешь, что у тебя сломалось при очередном рефакторинге или багфиксе.
Дальше, больше. Я начал анализировать, как я и многие другие, типично использую Spring/JBoss и понял, что legacy и широта возможности все усложняет. Можно реализовать упрощенный типичный Dependency Injection. Сказано, сделано. Добавил в свою библиотеку DI Framework. Мои знакомые смотрели мою реализацию и говорили что разобраться, как устроен Spring просто нереально, там полная жесть наследований и обверток, а у тебя все видно прям на первом уровне реализации и все понятно. Им было интересно, как работать с аннотациями и т. д.
Реализовав DI Framework я задумался над тем, что еще чуть-чуть и будет полноценный Enterprise Server. Осталось добавить ORM и Web-сервер с MVC, REST и security. Все в лучших традициях, так сказать. И меня затянуло. Еще неделька вечерами после работы, ссоры с женой, и получился Simplified Enterprise Server. Я не придерживался стандартов JavaEE, так как писал, как бы мне казалось, было удобно и понятно использовать. Сам я на работе использую Spring Boot, Spring Data, JPA 2.0, Spring MVC, Spring Rest, Spring Security. До этого делал проект на JBoss, видел другую сторону JEE, так сказать. Но вся это универсальность и гибкость конечно в тему. Но когда тебе нужно быстро накидать прототип в стиле JEE или тебе нужно научится кодить серьездные проекты на Java, окунаться в мир Spring, Hibernate и т.д. долго и кропотливо. Единственная альтернатива это Spring Boot, но реально там много происходит скрыто от тебя и если ты не знаешь как работает Spring под капотом, это только тебя тормозит, так как любой шаг в лево или в право это полный нырок в детали…
В итоге код фреймворка на гитхабе github.com/evgenyigumnov/common
Пример веб-сервиса использующего этот фреймворк на гитхабе github.com/evgenyigumnov/example и в онлайне его тоже можно посмотреть java.igumnov.com:8181 Пользователь: demo Пароль: demo
Структура примера:
./:
pom.xml
./javascript:
user.js
./pages:
index.html
layout.html
login.html
./sql:
1.sql
./src/main/java/com/igumnov/common/example:
App.java
ExampleUser.java
pom.xml
<dependencies>
<!-- подключаем наш фреймворк -->
<dependency>
<groupId>com.igumnov</groupId>
<artifactId>common</artifactId>
<version>3.15</version>
</dependency>
<!-- подключаем БД -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.187</version>
</dependency>
<!-- подключаем Bootstrap, AnglularJS и тд из webjars проекта -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angular-ui-bootstrap</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>angularjs</artifactId>
<version>1.3.8</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>
App.java
public class App {
public static void main(String[] args) throws Exception {
ORM.connectionPool("org.h2.Driver", "jdbc:h2:mem:test", "SA", "", 1, 3); // Создаем пул коннекций к БД (максимум 3 коннекта)
ORM.applyDDL("sql"); // Накатываем на базу объявления таблиц или оно это пропускает если уже далало
WebServer.init("localhost", 8181); // Задаем начальные параметры веб-сервера
WebServer.security("/login", "/login", "/logout"); // Говорим что у нас включена безопасность которая должна работать по URL-ам
WebServer.addRestrictRule("/*", new String[]{"user_role"}); // Ограничиваем доступ только для пользователям с ролью user_role
WebServer.addAllowRule("/static/*"); // Даем доступ для всех к статическому контенту
WebServer.addClassPathHandler("/static", "META-INF/resources/webjars"); // Указываем откуда брать этот статический контент из classpath от webjars
WebServer.addAllowRule("/js/*"); // Даем доступ для всех к нашим Java Script-ам
WebServer.addStaticContentHandler("/js", "javascript"); // Указываем в какой папке на винте лежат наши Java Script
WebServer.addTemplates("pages",0); // Указываем в какой папке на винте лежат шаблоны страниц
// Добавляем контроллер по урл "/", который добавляет в модель текущее время и говорит, что нужно отобразить index.html
WebServer.addController("/", (request, model) -> {
model.put("time", new Date());
return "index";
});
// Добавляем контроллер по урл "/login", который говорит, что нужно отобразить login.html
WebServer.addController("/login", (request, model) -> {
return "login";
});
// Добавляем REST-контроллер по урл "/rest/user" и указываем что могут методом POST/PUT прислать JSON-объект типа ExampleUser.class
WebServer.addRestController("/rest/user", ExampleUser.class, (request, postObj) -> {
switch (request.getMethod()) {
case (WebServer.METHOD_GET): // Прилетел GET запрос
ArrayList<Object> users;
try {
users = ORM.findAll(ExampleUser.class); // Извлекаем список пользователей
} catch (Exception e) {
throw new WebServerException(e.getMessage()); // Словили ошибку, которая будет сериализована в JSON
}
return users; // Возвращаем массив пользователей который сам сериализуется в JSON
case (WebServer.METHOD_POST): // Прилетел POST запрос
ExampleUser ret = null;
try {
ret = (ExampleUser) ORM.insert(postObj); // Вставляем его в БД
} catch (Exception e) {
throw new WebServerException(e.getMessage());
}
return ret; // В случае успеха просто возвращаем добавленного пользователя в виде JSON
case (WebServer.METHOD_DELETE): // Прилетел DELETE запрос
ExampleUser user;
try {
user = (ExampleUser) ORM.findOne(ExampleUser.class, request.getParameter("userName")); // Ищем юзера в БД
if(user.getUserName().equals("demo")) { // Если юзер demo не даем удалять
throw new WebServerException("You cant delete user demo");
} else {
ORM.delete(user); // Иначе шлем в БД delete-запрос
}
} catch (Exception e) {
throw new WebServerException(e.getMessage()); // Словили ошибку, которая будет сериализована в JSON
}
return user; // Возвращаем JSON юзера, которого удалили в случае успеха данной операции
default:
throw new WebServerException("Unsupported method"); // Ругаемся если прилетел запрос иного типа, например PUT или иной
}
});
ArrayList<Object> users = ORM.findAll(ExampleUser.class); // Берем из БД всех пользователей
if (users.size() == 0) { // В таблице с пользователями пусто
ExampleUser user = new ExampleUser();
user.setUserName("demo");
user.setUserPassword("demo");
ORM.insert(user); // Добавляем demo/demo пользователя в БД
WebServer.addUser("demo", "demo", new String[]{"user_role"}); // Сообщаем веб-серверу что есть пользователь demo/demo с ролью user_role
}
users.stream().forEach((user) -> { // Перебираем список пользователей полученный из БД
ExampleUser u = (ExampleUser) user;
WebServer.addUser(u.getUserName(), u.getUserPassword(), new String[]{"user_role"}); // Сообщаем веб-серверу о новом пользователе с ролью user_role
});
WebServer.start(); // Если до этого места кода дошло управление и ничего не вывалилось по Exception, то стартуем веб-сервер :)
}
}
ExampleUser.java
// Данный класс используется для JSON сериализации и десериализации и также для меппинга в БД
public class ExampleUser {
@Id(autoIncremental = false) // Необходимо ORM знать какое поле является Primary Key и генерится ли при insert значение этого поля
private String userName;
private String userPassword;
...
}
1.sql
# Создаем таблицу в БД где будем хранить через ORM объекты типа ExampleUser.class
CREATE TABLE ExampleUser (userName VARCHAR(255) PRIMARY KEY, userPassword VARCHAR(255))
login.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<!-- Указываем что нужно использовать декоратор layout из layout.html -->
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layout">
<body>
<!-- Объявляем наш контент блок который будет подставлен в layout.html -->
<div layout:fragment="content">
<form name="form" action="/j_security_check" method="POST">
<div class="modal-header">
<h3 class="modal-title">Login</h3>
</div>
<div class="modal-body">
<div class="form-group">
<input type="text" name="j_username" class="form-control" value="" placeholder="Login"/>
</div>
<div class="form-group">
<input type="password" name="j_password" class="form-control" placeholder="Password"/>
</div>
<div class="form-group">
<button type="submit" id="login" class="btn btn-primary">OK</button>
</div>
</div>
</form>
</div>
</body>
</html>
index.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<!-- Указываем что нужно использовать декоратор layout из layout.html -->
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layout">
<body>
<!-- Объявляем наш контент блок который будет подставлен в layout.html -->
<div layout:fragment="content">
<!-- Подключаем наш контроллер на AngularJS-->
<script src="/js/user.js"></script>
<h1 th:text="${time}"></h1> // Выводим текущее время переданное в модель
<!-- Обозначаем область действия нашего контроллера UserCtrl -->
<div ng-controller="UserCtrl">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Password</th>
<th></th>
</tr>
</thead>
<tbody>
<!-- В цикле заполняем таблицу пользователями -->
<tr ng-repeat="user in users">
<td>{{user.userName}}</td>
<td>{{user.userPassword}}</td>
<!-- По клику на крестик вызываем функцию на контроллере для удаления пользователя -->
<td><a href="#"><span class="glyphicon glyphicon-remove" tooltip="Delete" ng-click="deleteUser(user)"/></a></td>
</tr>
</tbody>
</table>
<div ng-model="user">
<!-- Форма добавления пользователя -->
<div class="form-group">
<input type="text" class="form-control" ng-model="user.userName" placeholder="Login"/>
</div>
<div class="form-group">
<input type="password" class="form-control" ng-model="user.userPassword" placeholder="Password"/>
</div>
<div class="form-group">
<!-- По клику на кнопке вызываем функцию в контроллере добавляющую пользователя -->
<button class="btn btn-primary" ng-click="addUser(user)">Add</button>
</div>
</div>
</div>
</div>
</body>
</html>
layout.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<!-- Область действия нашего приложения на AngularJS -->
<html ng-app="com.igumnov.common.example">
<head>
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/3.3.1/css/bootstrap.min.css" />
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<script src="/static/angularjs/1.3.8/angular.min.js"></script>
<script src="/static/angularjs/1.3.8/angular-resource.min.js"></script>
<script src="/static/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script>
<div class="container">
<!-- Сюда будет вставляться контентный блок -->
<div layout:fragment="content"></div>
</div>
</body>
</html>
user.js
angular.module('com.igumnov.common.example', ['ui.bootstrap', 'ngResource'])
.factory('User', ['$resource', function ($resource) { // Объявляем REST-ресурс User
return $resource('/rest/user', {}, {
list: { // Список юзеров
method: 'GET',
cache: false,
isArray: true // Результат вызова массив
},
add: { // Добавляем юзера
method: 'POST',
cache: false,
isArray: false // Результат вызова один объект
},
delete: { // Удаляем юзера
method: 'DELETE',
cache: false,
isArray: false // Результат вызова один объект
}
});
}])
.controller('UserCtrl', function ($scope, User) { // Обьявляем наш контроллер UserCtrl
$scope.users = User.list({}); // Заполняем список пользователя при инициализации контроллера
$scope.addUser = function (user) { // Функция добавления пользователя
User.add({},user,function (data) { // Дергаем REST-интерфейс
$scope.users = User.list({}); // В случае успеха, перезаполняем список пользователей
}, function (err) {
alert(err.data.message); // В случае ошибки, выводим ошибку
});
}
$scope.deleteUser = function (user) { // Функция удаления пользователя
User.delete({"userName" : user.userName},user,function (data) { // Дергаем REST-интерфейс
$scope.users = User.list({}); // В случае успеха, перезаполняем список пользователей
}, function (err) {
alert(err.data.message); // В случае ошибки, выводим ошибку
});
}
});
В заключении, буду рад любой критике и предложению по улучшению кода библиотеки. Для себя я получил профит в разминании мозга при написание библиотеки и использовании замыканий/лямбд. Иногда скучно писать коммерческие продукты, хочется создать свой космический корабль. Не стесняйтесь форкать мою либу и самим ее модифицировать под свои нужды. Она достаточно проста и легка для внесения в нее модификаций. Буду признателен, если вы будете присылать пулл-реквесты, чтобы ваши доработки вносились в библиотеку. Я лично настроен достаточно быстро их проверять и принимать. Я просто фанатик-программер, меня это втыкает. Люблю кодить!
PS Да-да, я не люблю писать javacode, шлите пулл-реквесты с ним, сейчас по коду либы очень понятно, что каждый метод ее делает…
Комментарии (36)
yroman
28.05.2015 23:25+6ArrayList Object — Вы это серьёзно?
С каких это пор набор обёрток для автосборки sql запроса стал называться ORM?
PS: И чем вам джус не угодил в качестве DI контейнера?igumnov Автор
29.05.2015 04:59-1На что заменить ArrayList? :) Мне в первую очередь нужно было что бы с результата можно было вызвать .stream() а Spring Data к сожалению возвращает Iterable :-( В свое время это было удобно, а сейчас java 1.8 и удобнее работать со стримами, а не писать циклы.
До это не до ORM. Разумный компромис между меппинга объекта в SQL и автогенерации типовых SQL-запросов.
Джус — ок. Просто захотелось свое написать :)
leventov
29.05.2015 00:51+3legacy и широта возможности все усложняет
Я так понимаю, это ваша ключевая претензия к существующим энтерпрайз-комбайнам. А можно уточнить, что значит «все усложняет»? Усложняет исполнение, все тормозит? Или усложняет освоение этих самых комбайнов и их возможностей? Или усложняет использование, надо писать много кода в простых случаях? Или усложняет что?
Вы должны очень четко ответить на эти вопросы, а также четко показать, чем ваша разработка лучше в этих моментах. Только тогда, есть шанс, что кто-то реально заинтересуется ее использованием, и уж тем более развитием.igumnov Автор
29.05.2015 05:14Я так понимаю, это ваша ключевая претензия к существующим энтерпрайз-комбайнам
У меня никаких претензий — я сам их использую в коммерческих проектах.
А можно уточнить, что значит «все усложняет»?
Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.
Или усложняет освоение этих самых комбайнов и их возможностей?
Еще один аргумент в пользу моей либы, что-то не нравится как у меня сделано, бери изменяй или дорабатывай, у меня обьем кода маааленький. А комбайны изучаем, используем, пишем им баги и хотелки. Ждем когда внесут изменения…
Или усложняет использование, надо писать много кода в простых случаях? Или усложняет что?
Для быстрого прототипирования приложения тяжеловаты они. Особенно для начинающих программистов. Вот основной аргумент.
Вы должны очень четко ответить на эти вопросы, а также четко показать, чем ваша разработка лучше в этих моментах. Только тогда, есть шанс, что кто-то реально заинтересуется ее использованием, и уж тем более развитием.
Посмотрите мои ответы на другие коменты под статьей, может я как-то тут чего не до пояснил в этом ответе. Мне моя либа нравится, она прямая, простая, прозрачная. Использует лямбды, стримы. Это сокращает код. Но естественно код не соответствует никаким JEE стандартам и не является универсальным, типа сменил мой DI на другой — не выйдет. Подставить другую БД — будет валиться на синтаксисе SQL. Подпихнуть под другой Сервлет контейнер — не пройдет, ибо все написано не по Севрлет API.leventov
29.05.2015 06:12+4Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.
То есть, вам не нравится синтаксис деклараций JPA? Ну есть же миллионы других. Писать на SQL — это не конкурентное преимущество, потому что это могут делать все
Еще один аргумент в пользу моей либы, что-то не нравится как у меня сделано, бери изменяй или дорабатывай, у меня обьем кода маааленький. А комбайны изучаем, используем, пишем им баги и хотелки. Ждем когда внесут изменения…
1) Это аргумент в пользу любой маленькой либы, коих миллиард. Почему вы сами не доработали чью-то другую либу?
2) Никто не хочет писать любую функициональность сам, потому что это огромная цена — по тестированию, поддержке, багам, документации и т. д. Если в «комбайне» нет возможности сделать что-то напрямую, но можно совместить 3-4 другие функции, возможно неоптимально, возможно через жопу, но добиться требуемого поведения — любой разумный человек предпочтет этот вариант, потому что все куски по отдельности протестированы и развиваются авторами комбайна, а не в нашей конторе, где и так дел не впроворот.
Для быстрого прототипирования приложения тяжеловаты они. Особенно для начинающих программистов. Вот основной аргумент.
Опять же, есть кучи таких же маленьких фреймворков «для прототипирования», почему не взяли их, а запилили еще один?
Использует лямбды, стримы.
Вот это уже что-то рабочее. Похоже на фишку. Я рекомендую вам либо поразмыслить насчет моего совета по поводу других JVM-языков, либо делать акцент на своей либе, именно как на «энтерпрайз задачи с использованием мощи лямбд».
norguhtar
29.05.2015 08:56+1За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.
SQL в JPA пихать как-то никто не мешает. Чем я регулярно занимаюсь.
Borz
29.05.2015 10:43Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.
тот же MyBatis чем не устроил?
relgames
29.05.2015 01:39+2Жесть. С другой стороны, у вас, похоже, очень много свободного времени, тут я вам завидую :)
Не пробовали написать свой компилятор Java? ;)
p.s.
Последний Spring Boot + Spring Data очень похож на то, что вы описали в примерах, и даже еще проще.igumnov Автор
29.05.2015 05:04Java компилятор — легко! Шучу.
А между тем вот вроде в 1.8 Java появились стримы даже в File NIO. Но почему я не могу от объекта String взять stream и поработать с буквами? Пришлось писать свою обвертку Strings:
String s = «some line»;
Strings.stream(s).forEach(© -> {
// do something by each char in string
});
На счет спринг бута и спринг дата — да я даже пример писал посевного проекта github.com/evgenyigumnov/spring-boot-security-rest-thymleaf-angularjs-bootstrap-jasperreports-jpa-seed
Но между тем спринг был написал давно и спринг дата возвращает итерейбл и с него напрямую .stream не возьмешь. Только обвертывать.
Потом Spring MVC требует на каждый контроллер писать класс и промечать все анотациями — у меня же все на лямбдах — меньше файлов и букв.Moxa
29.05.2015 05:40+2"hello".chars() .mapToObj(i -> (char)i) .forEach(System.out::println);
Why does Iterable not provide stream() and parallelStream() methods?
leventov
29.05.2015 06:03Мне кажется, вам стоит поглядеть в сторону JVM-языков с сахарочком, например Groovy, Kotlin, Scala.
Iterable нельзя преобразовать в Stream через точку — согласен, бывает в Java такое неудобство. Но в других языках можно определить методы, расширяющие существующий интерфейс (extension methods). Или другие, но аналогичные по результату приемы: вы получаете «точку». Или вообще сразу Stream, если расширите спринговый класс.
«Писать классы и помечать аннотациями» — для сокращения всякого такого унылого боилерплейта и придуманы языки, перечисленные выше, там есть соответствующие возможности.igumnov Автор
29.05.2015 07:10+1Мне нравится Scala (даже книжку прочитал про нее), но меня смущает пару моментов:
1. Что-то на мавен репозитарии утих рост либы с языком
2. Они так и не исправили, то что компилится долго и не известно когда исправят
3. У нас в России порядка 10-20 вакансий на всю страну на Scala и зарплата там по сравнению с Java в среднем +10 тыс рубsolver
29.05.2015 12:48+1>1. Что-то на мавен репозитарии утих рост либы с языком
Если это про размер самого языка. То начиная с 12-й версии они разделят разные части языка по пакетам.
>2. Они так и не исправили, то что компилится долго и не известно когда исправят.
Очень слабый аргумент. Там есть инкрементальная компиляция. Так что если вы постоянно компилируете весь проект, то вы что-то делаете не так.
>3. У нас в России порядка 10-20 вакансий на всю страну на Scala и зарплата там по сравнению с Java в среднем +10 тыс руб
Если вас интересует именно заработок. То в программисты вообще не следовало идти. Есть направления в которых зарабатывают сильно больше… при чем очень сильно больше))
ShadowsMind
29.05.2015 07:13Еще Ceylon говорят весьма не плох. Сам правда не пробовал, т.к. Scala захватила мой разум )
darkazazello
29.05.2015 12:42+1Открыл гитхаб:
try { tx = ORM.beginTransaction(); return tx.update(obj); } finally { if (tx != null) { tx.commit(); } }
аааааааааааааааа, мои глаза
Закрыл гитхабigumnov Автор
29.05.2015 12:48-2Да — ошибка логическая — надо бы сначала закомитеть -)))
igumnov Автор
29.05.2015 12:53msd
29.05.2015 13:09+3«ничего не изменилось»
igumnov Автор
29.05.2015 13:27-2Всмысле? Если при попытке коммита свалится, то эксепшен уйдет на верх а не вернется ret обьект
Хотя вообще зачем финал? -)igumnov Автор
29.05.2015 13:32Fixed github.com/evgenyigumnov/common/commit/a495e2651ce3a758154c728661dd422590217efd
Вот он бездумный копи паст прокрался -)relgames
30.05.2015 19:27Я ж и говорю — у вас много свободного времени, в том числе и в будущем, когда придется фиксить похожие баги. :)
Spring и т.д. бывает не очень удобен, но это проект, которым пользуются сотни тысяч людей, и баги там вылавливают и фиксят.
Конечно, баги есть и там, но вопрос в количестве и в уровне.
darkazazello
29.05.2015 12:54Но у вас же еще и транзакции не делают rollback, из метода выбрасывается куча эксепшенов, я думаю часть которых можно было и на месте обработать
igumnov Автор
29.05.2015 12:59Ну можно было и внутри отработать часть :) Кодил то быстрее-быстрее, пусть там на верху разбираются, кто поймает эксепшен -)
darkazazello
29.05.2015 13:10А что же делать с таким кодом?
} catch (Exception e) {}
igumnov Автор
29.05.2015 13:23Каленым железом выжигаем github.com/evgenyigumnov/common/commit/ad0c807e03f9ca398bc9c4b82693437c6c32f629
darkazazello
29.05.2015 13:32А почему бы тогда не заиспользовать try-with-resources?
igumnov Автор
29.05.2015 13:35Да можно, надо будет потом дописать «Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.»
umputun
30.05.2015 08:25+4Я люблю смотреть на разные commons, но тут открыл случайным образом 3 файла и быстро-быстро закрыл. Желание написать полезное для себя и поделится этим со всеми весьма похвально, но все, что написано например в File, можно было не писать, если знать про стандартные Files и Path.
А в Strings — ну это просто не для слабонервных, хотя тут даже есть своя ирония — видимо из незнания базовой стандартной библиотеки пишется самым заскорузлым императивным образом посимвольное копирование строки в свежий лист и на это дело отдается стрим, чтоб потом этим модно воспользоваться :)
Сакральный смысл Tasks для меня вообще скрыт. Единственное объяснение, зачем оно вообще написано, это то, что автору слово start нравится больше чем submit. Ну а прибитый гвоздями fixedThreadPool на фоне того, что автор вероятно написал где-то и свой DI, вызывает еще одно недоумение.
igumnov Автор
30.05.2015 09:441. Я не ставил себе цели написать идеальный код, мне присылают пулл реквесты в которых идут улучшения, я их тестирую и применяю. Много кода я копи-пастил с Stack Overflow внося в него модификации что бы работало под моими методами. Сами прекрасно знаете что большая часть аудитории там не знакома с тем что в File NIO в Java 1.8 появилось возможность работать со стримами в функциональном стиле, а не использовать Walker-ов для обхода файлов. Не нравится, шлите пул реквест где реализация в кошерном стиле :)
2. Strings — поправил github.com/evgenyigumnov/common/commit/4c0570e8a9a29c765e5e54ecc69a99c5454bdf0b
3. В целом все ваши замечания верны, либа эта от говнодела для говноделов :)
Power
30.05.2015 17:55+3Добавлю критики в пользу стандартных решений.
Log.java
Ещё один логгер? Чем вам логгеры-то не угодили? Мне кажется, гораздо лучше будет использовать какой-нибудь slf4j, ставший уже стандартом де-факто.
Number.java
Делает то же, что и ThreadLocalRandom, только хуже (сравните результат вашегоNumber.randomIntByRange(0, -100)
с аналогичным вызовом ThreadLocalRandom).
Benchmark.java
И тут я посмотрел на тесты.
Ну разве так можно делать?Из TimeTest.java:
@Test public void testTimerStop() throws Exception { try { Benchmark.timerStop(); } catch (Exception e) { assertTrue(true); } }
А еслиBenchmark.timerStop()
не выкинет Exception, то тест пройдёт. Не то, что вы хотели. Не говоря уже о том, что может вылететь не TimeException. Правильнее так:
@Test(expected = TimeException.class) public void testTimerStop() throws TimeException { Benchmark.timerStop(); }
XlebNick
Спасибо за то, что поделились опытом, очень полезно! + отдельное спасибо за либу :)
p.s. насчет документации, на практике знаю, что ее написание в достаточной мере неприятно, но она крайне полезна для программистов, использующих Вашу либу и для Вас самих даже. Я постараюсь популлить в свободное время, ибо сам я на эти грабли уже наступал.
igumnov Автор
Спасибо. Рад что понравилось. Буду ждать.