Все началось с того, что я решил написать свою Java Common библиотеку. Очень часто для типичных задач на Stack Overflow находятся решения в 3-5-10 строк кода. Копипастить себе в проекты надоело. Решил вынести это в отдельную библиотеку, которую можно сделать Open Source и использовать в текущих и будущих своих проектах. Безусловно есть такие хорошие либы как Google Guava и Apache Commons, которые я тоже использую в работе, но они достаточно «правильные» и гибкие, что выливается не в одну строчку кода.

В итоге вечерами дома в свободное от работы время я покрыл за 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)


  1. XlebNick
    28.05.2015 22:05

    Спасибо за то, что поделились опытом, очень полезно! + отдельное спасибо за либу :)

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


    1. igumnov Автор
      29.05.2015 04:56

      Спасибо. Рад что понравилось. Буду ждать.


  1. yroman
    28.05.2015 23:25
    +6

    ArrayList Object — Вы это серьёзно?

    С каких это пор набор обёрток для автосборки sql запроса стал называться ORM?

    PS: И чем вам джус не угодил в качестве DI контейнера?


    1. igumnov Автор
      29.05.2015 04:59
      -1

      На что заменить ArrayList? :) Мне в первую очередь нужно было что бы с результата можно было вызвать .stream() а Spring Data к сожалению возвращает Iterable :-( В свое время это было удобно, а сейчас java 1.8 и удобнее работать со стримами, а не писать циклы.

      До это не до ORM. Разумный компромис между меппинга объекта в SQL и автогенерации типовых SQL-запросов.

      Джус — ок. Просто захотелось свое написать :)


    1. gurinderu
      29.05.2015 10:24

      Я думал вы скажете про dagger2. А тут guice)


      1. igumnov Автор
        29.05.2015 13:38

        есть еще code.google.com/p/tinydi :)


  1. leventov
    29.05.2015 00:51
    +3

    legacy и широта возможности все усложняет

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

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


    1. igumnov Автор
      29.05.2015 05:14

      Я так понимаю, это ваша ключевая претензия к существующим энтерпрайз-комбайнам

      У меня никаких претензий — я сам их использую в коммерческих проектах.

      А можно уточнить, что значит «все усложняет»?

      Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.

      Или усложняет освоение этих самых комбайнов и их возможностей?

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

      Или усложняет использование, надо писать много кода в простых случаях? Или усложняет что?

      Для быстрого прототипирования приложения тяжеловаты они. Особенно для начинающих программистов. Вот основной аргумент.

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

      Посмотрите мои ответы на другие коменты под статьей, может я как-то тут чего не до пояснил в этом ответе. Мне моя либа нравится, она прямая, простая, прозрачная. Использует лямбды, стримы. Это сокращает код. Но естественно код не соответствует никаким JEE стандартам и не является универсальным, типа сменил мой DI на другой — не выйдет. Подставить другую БД — будет валиться на синтаксисе SQL. Подпихнуть под другой Сервлет контейнер — не пройдет, ибо все написано не по Севрлет API.


      1. leventov
        29.05.2015 06:12
        +4

        Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.

        То есть, вам не нравится синтаксис деклараций JPA? Ну есть же миллионы других. Писать на SQL — это не конкурентное преимущество, потому что это могут делать все

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

        1) Это аргумент в пользу любой маленькой либы, коих миллиард. Почему вы сами не доработали чью-то другую либу?
        2) Никто не хочет писать любую функициональность сам, потому что это огромная цена — по тестированию, поддержке, багам, документации и т. д. Если в «комбайне» нет возможности сделать что-то напрямую, но можно совместить 3-4 другие функции, возможно неоптимально, возможно через жопу, но добиться требуемого поведения — любой разумный человек предпочтет этот вариант, потому что все куски по отдельности протестированы и развиваются авторами комбайна, а не в нашей конторе, где и так дел не впроворот.

        Для быстрого прототипирования приложения тяжеловаты они. Особенно для начинающих программистов. Вот основной аргумент.

        Опять же, есть кучи таких же маленьких фреймворков «для прототипирования», почему не взяли их, а запилили еще один?

        Использует лямбды, стримы.

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


      1. norguhtar
        29.05.2015 08:56
        +1

        За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.

        SQL в JPA пихать как-то никто не мешает. Чем я регулярно занимаюсь.


      1. Borz
        29.05.2015 10:43

        Ну например JPA дает кросс БД совместимость. За это ты платишь тем, что приходится приседать на мета-языке JPA. Мой ORM он не дает кросс БД совместимость и можно писать SQL для меппинга объектов зависимый от БД.

        тот же MyBatis чем не устроил?


  1. relgames
    29.05.2015 01:39
    +2

    Жесть. С другой стороны, у вас, похоже, очень много свободного времени, тут я вам завидую :)
    Не пробовали написать свой компилятор Java? ;)

    p.s.
    Последний Spring Boot + Spring Data очень похож на то, что вы описали в примерах, и даже еще проще.


    1. igumnov Автор
      29.05.2015 05:04

      Java компилятор — легко! Шучу.
      А между тем вот вроде в 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 требует на каждый контроллер писать класс и промечать все анотациями — у меня же все на лямбдах — меньше файлов и букв.


      1. 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?


        1. igumnov Автор
          29.05.2015 07:10

          Огонь :)


      1. leventov
        29.05.2015 06:03

        Мне кажется, вам стоит поглядеть в сторону JVM-языков с сахарочком, например Groovy, Kotlin, Scala.

        Iterable нельзя преобразовать в Stream через точку — согласен, бывает в Java такое неудобство. Но в других языках можно определить методы, расширяющие существующий интерфейс (extension methods). Или другие, но аналогичные по результату приемы: вы получаете «точку». Или вообще сразу Stream, если расширите спринговый класс.

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


        1. igumnov Автор
          29.05.2015 07:10
          +1

          Мне нравится Scala (даже книжку прочитал про нее), но меня смущает пару моментов:
          1. Что-то на мавен репозитарии утих рост либы с языком
          2. Они так и не исправили, то что компилится долго и не известно когда исправят
          3. У нас в России порядка 10-20 вакансий на всю страну на Scala и зарплата там по сравнению с Java в среднем +10 тыс руб


          1. solver
            29.05.2015 12:48
            +1

            >1. Что-то на мавен репозитарии утих рост либы с языком
            Если это про размер самого языка. То начиная с 12-й версии они разделят разные части языка по пакетам.

            >2. Они так и не исправили, то что компилится долго и не известно когда исправят.
            Очень слабый аргумент. Там есть инкрементальная компиляция. Так что если вы постоянно компилируете весь проект, то вы что-то делаете не так.

            >3. У нас в России порядка 10-20 вакансий на всю страну на Scala и зарплата там по сравнению с Java в среднем +10 тыс руб
            Если вас интересует именно заработок. То в программисты вообще не следовало идти. Есть направления в которых зарабатывают сильно больше… при чем очень сильно больше))


            1. igumnov Автор
              29.05.2015 13:42

              Я просто хочу что бы и было интересно и что бы деньги были хорошие -)


        1. ShadowsMind
          29.05.2015 07:13

          Еще Ceylon говорят весьма не плох. Сам правда не пробовал, т.к. Scala захватила мой разум )


  1. darkazazello
    29.05.2015 12:42
    +1

    Открыл гитхаб:

    try {
                tx = ORM.beginTransaction();
                return tx.update(obj);
    
            } finally {
                if (tx != null) {
                    tx.commit();
                }
            }
    

    аааааааааааааааа, мои глаза
    Закрыл гитхаб


    1. igumnov Автор
      29.05.2015 12:48
      -2

      Да — ошибка логическая — надо бы сначала закомитеть -)))


      1. igumnov Автор
        29.05.2015 12:53

        1. msd
          29.05.2015 13:09
          +3

          «ничего не изменилось»


          1. igumnov Автор
            29.05.2015 13:27
            -2

            Всмысле? Если при попытке коммита свалится, то эксепшен уйдет на верх а не вернется ret обьект
            Хотя вообще зачем финал? -)


            1. igumnov Автор
              29.05.2015 13:32

              Fixed github.com/evgenyigumnov/common/commit/a495e2651ce3a758154c728661dd422590217efd
              Вот он бездумный копи паст прокрался -)


              1. relgames
                30.05.2015 19:27

                Я ж и говорю — у вас много свободного времени, в том числе и в будущем, когда придется фиксить похожие баги. :)

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


  1. darkazazello
    29.05.2015 12:54

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


    1. igumnov Автор
      29.05.2015 12:59

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


      1. darkazazello
        29.05.2015 13:10

        А что же делать с таким кодом?

         } catch (Exception e) {}


        1. igumnov Автор
          29.05.2015 13:23

          1. darkazazello
            29.05.2015 13:32

            А почему бы тогда не заиспользовать try-with-resources?


            1. 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.»


  1. umputun
    30.05.2015 08:25
    +4

    Я люблю смотреть на разные commons, но тут открыл случайным образом 3 файла и быстро-быстро закрыл. Желание написать полезное для себя и поделится этим со всеми весьма похвально, но все, что написано например в File, можно было не писать, если знать про стандартные Files и Path.

    А в Strings — ну это просто не для слабонервных, хотя тут даже есть своя ирония — видимо из незнания базовой стандартной библиотеки пишется самым заскорузлым императивным образом посимвольное копирование строки в свежий лист и на это дело отдается стрим, чтоб потом этим модно воспользоваться :)

    Сакральный смысл Tasks для меня вообще скрыт. Единственное объяснение, зачем оно вообще написано, это то, что автору слово start нравится больше чем submit. Ну а прибитый гвоздями fixedThreadPool на фоне того, что автор вероятно написал где-то и свой DI, вызывает еще одно недоумение.


    1. igumnov Автор
      30.05.2015 09:44

      1. Я не ставил себе цели написать идеальный код, мне присылают пулл реквесты в которых идут улучшения, я их тестирую и применяю. Много кода я копи-пастил с Stack Overflow внося в него модификации что бы работало под моими методами. Сами прекрасно знаете что большая часть аудитории там не знакома с тем что в File NIO в Java 1.8 появилось возможность работать со стримами в функциональном стиле, а не использовать Walker-ов для обхода файлов. Не нравится, шлите пул реквест где реализация в кошерном стиле :)
      2. Strings — поправил github.com/evgenyigumnov/common/commit/4c0570e8a9a29c765e5e54ecc69a99c5454bdf0b
      3. В целом все ваши замечания верны, либа эта от говнодела для говноделов :)


  1. 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();
    }