Search
Write a publication
Pull to refresh
4
0.1

Уверенный пользователь холодильника

Send message
Так вот этот метод при наследовании можно было бы и переопределить.

К сожалению, нет, нельзя — мякотка проблемы находится не там, где происходит setTime, а внутри метода format(Calendar). И состоит она в том, что нет никакого такого метода — есть метод, работающий с полем Calendar, загружая его значение из ссылки this.calendar. Даже если бы мы что-то переопределили и клонировали бы, нам пришлось клонированный экземпляр вписать в поле this.calendar — нас это не спасёт от гонки. Тут может помочь разве что сделать сам публичный внешний метод format synchronized — но решение неполное, т.к. остаются все прочие способы получить доступ к this.calendar, которые уже просто так синхронизировать не получится, бо мы не имеем над ними контроля. Решение поможет в моём псевдокоде, но создаст только иллюзию безопасности в общем случае.


Вы ведь привели проблемный участок кода самого класса SimpleDateFormat

Я привёл только самый простой пример. Есть ещё варианты, когда треды вызывают formatter::setTimeZone или formatter::setLenient перед вызовом formatter::format, и там тоже будут такие же по сути проблемы.


LSP

Liskov Substitution Principle


… Это был настоящий ад...

Этот опыт, что вы описываете, не о том, что сложно переопределить некий тип из JDK — он говорит о том, что сложно переиспользовать private-код из JDK. Здесь нет явной проблемы — в Java действительно не получится переопределить части, для которых не предусмотрена явно такая возможность, и это относится к вообще любому коду на Java.


Однако, ничто вам, в принципе, не помешает сделать


public class MyImmutableList<E>
  extends ArrayList<E> {
  @Override
  public void set(int index, E element) {
    if (!Objects.equals(element, get(index))) {
      throw new UnsupportedOperationException();
    }
  }
}

И как-то так получить список, в который можно добавить элемент, но нельзя заменить элемент, при этом сам список можно будет передавать почти в любой код, и вести он себя будет в соответствии с контрактами ArrayList(+ ваша надстройка) — можно итерироваться, добавлять/удалять элементы, брать размер списка, но попытка заменить элемент или отсортировать — приведут к ошибке.

если у нас однопоточное приложение (это всё-таки наиболее частый случай), то проблем с этим классом нет?

Да. Если компилятор может доказать, что на доступ к полю не будет конкуренции, то он может и synchronized-блоки убрать. Ну, или облегчить.


Я понимаю, что класс встроенный, и наследоваться от него — наверное идея не очень

Нет проблемы наследоваться от любого не-final типа в JDK, если соблюдать LSP. Но если идея здесь — сделать собственный тип с мониторами на внутренний Calendar — думаю, гарантий не прибавится, т.к. нужно видеть полный набор операций, который конкретный поток делает с formatter'ом.


Конкретно эта именно проблема может — и должна, раз уж разработчики JDK не решают сами — быть решена на стороне вызывающего кода, например, синхронизацией на сам formatter — тогда будут гарантии, что каждый поток владеет состоянием formatter'а безраздельно, но при этом и программа становится, фактически, однопоточной:


synchronized(formatter) {
  System.out.println(String.format(
      "Days in future %d, date %s",
      val,
      formatter.format(theDate);
  ));
}

Верно ли я понимаю, что экземпляр Calendar.getInstance() — один на все потоки?

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


Или объекты могут копироваться в кэш-память процессора как и скалярные переменные?

Честно говоря, не уверен. Насколько я понимаю, так копироваться в кеш может ссылка на объект, и при этом не даётся никаких гарантий касаемо happens-before если ссылки/поля не выполняются в контексте какого-нибудь монитора, но я могу сильно ошибаться.


Зато вот нашёл статью про модель памяти, может, там есть ответ: https://habr.com/ru/company/golovachcourses/blog/221133/

Выглядит вот так:
class SimpleDateFormat {
    private Calendar calendar = Calendar.getInstance();

    public String format(Date theDate) {
        calendar.setTime(theDate);

        return format(calendar);
    }
    
    private String format(Calendar calendar) {
        ...
    }
}

/**....*/

class Main {
  static SimpleDateFormat formatter =
                   SimlpeDateFormat`yyyy-MM-dd`;

  public static int main(String[] args) {
    ExecutorService exc =
            Executors.newFixedThreadPool(10);

    IntStream.range(0, 1000).forEach(val -> {
      exc.execute(() -> {
        Date theDate = Date.from(
            Instant.now().plus(val, DAYS)
        );
        System.out.println(String.format(
            "Days in future %d, date %s",
            val,
            formatter.format(theDate);
        ));
      });
    });
  }
  exc.shutdown();
  exc.awaitTermination(
      Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}


Мы собираемся печатать дату, которая отличается на val дней от сегодня. Но из-за вызова setTime внутри format(Date) на одном и том же объекте может получиться (а может и не получиться — именно поэтому баг такой гадкий) так, что напечатана будет какая-нибудь другая дата — например, если между вызовом setTime и format(Calendar) в одном треде произойдёт вызов setTime в другом треде, и это значение попадёт в кэш и будет загружено в первый тред. Тогда если сегодня — `2019-04-01`, мы можем увидеть в консоли что-то типа "Days in future 3, date 2019-04-26".
Тот же классический Java-вский DateTimeFormatter (или как он там называется…

Называется он SimpleDateFormat, и он действительно не безопасен — потому что на самом деле™ форматирует не java.util.Date, а вовсе даже java.util.Calendar — и оный является полем класса, и на него бывает гонка, о чём есть заметки во всех javadoc'ах по этому семейству классов.


А вот DateTimeFormatter добавлен в java 8, и он потокобезопасный, и это важно — а то так будут ходить слухи, что в "Java нельзя даже нормально время отформатировать в многопоточном приложении". Правда, им уже нельзя форматировать ни java.util.Date, ни java.util.Calendar, ну да туда им и дорога.

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


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

Вернитесь к моему первому комментарию в ветке, там, по-моему, практически то же самое и написано, с поправкой на постулат о возможной замене Controller'а Presenter'ом — и это, согласно цитате, как раз то, в чём я здесь не прав.
Зато загрузка данных с сервера, даже в простых приложениях — ответственность именно модели, и выходит, что без неё на самом-то деле никуда — разве что у вас там запрос синхронный (звоночек, кстати), и всё нафигачено прямо в одной и той же функции — и загрузка, и обновление DOM. А вот если функций несколько — высока вероятность, что одна будет носить функции модели, а вторая — функции представления. Ну а раз как-то модель с представлением связывается…

Простые приложения — это те, которые статический HTML грузят просто по URI, напрямую? Потому что для всех остальных не будет просто «загрузил и показал», с будет уже какая-то M, пришедшая с сервера, и какой-то V, куда можно данные «показать», и какой-то слой P, определяющий как именно M соотносится с V.
Ну, M и V нужны всегда, к примеру. Для readonly V нужен Presenter, вы правы. А вот для двусторонних взаимодействий уже не обойтись без хоть какого завалящего C. Даже если вы C напрямую не пишете.

Заметьте — если у вас C спрятан где-то глубоко в недрах, откуда наружу торчит только {myImportantProperty} — это вовсе не значит, что C у вас нет — просто он достаточно абстрактный чтобы понимать декларативные биндинги.
Так можно про любой паттерн проектирования расписать.
На деле же от того, что некоторые игнорируют границы применимости инструмента (а именно об этом вы говорите), сам инструмент плохим не становится.
Что это за контент у вас такой, который настолько лишён самоценности, что достаточно обычного пересказа?
И откуда такая святая вера в «недополучаемую прибыль» из-за злых поисковиков?
Отличный повод расписоздать общегосударственную систему-гаранта. Само собой, не бесплатную.
Ну всё правильно — Google ведь монетизирует индекс. Хочешь монетизировать и чтобы люди приходили к тебе когда ищут что-то похожее? Заплати за индексацию.
Почему, по-вашему мнению, владельцу контента можно, а владельцу базы индексов — нет? Или может всё-таки признаем, что сотрудничество взаимовыгодное, и по факту платить не должны ни авторы, ни агрегаторы?
Автоматизация+вложенные абстракции погубят нас всех :)

Всех, да. Но кто-то из "всех" потеряет средства к существованию, а кто-то — умрёт от передозировки денег.

Аналоги CompletableFuture в Java были и до восьмёрки, кому это очень надо было — делали свои костыли. Возьмите хотя бы Guava: ListenableFuture — это уже достаточно асинхронный код? А любая библиотека с EventLoop'ом внутри, включая JavaFX и Android?
Под капотом в CompletableFuture нет ничего из ряда вон выходящего — просто к моменту выхода 8 было достаточно высокое давление от простых разработчиков (мода, то есть), плюс убрали часть ритуалов при кормлении Future callback'ами, что и послужило отправной точкой добавления именно в стандартную библиотеку.

В общем, это будто вы сказали, что под Java реактивное программирование появилось только в 2018 с выходом Java 9 и тамошнего java.util.concurrent.Flow, при живом-то RxJava.
Будут устанавливать иглы для забора и анализа крови во все кнопки, которыми можно «дать согласие» в браузере.
Как в сказке про Белоснежку — она просто дала согласие на лицензионное соглашение пользования прялкой. Которое незамедлительно вступило в силу.
Раз уж теперь нажатие на кнопку в браузере является полноценным волеизъявлением — как предполагается бороться с, например, средствами автоматизации браузера?

Возможно ли доказать «недееспособность» компьютера, заражённого вирусами? Или произвольный вирус теперь сможет совершенно легально подписывать владельцев заражённого устройства на какие-нибудь услуги?

Как быть с legally-binding кнопками, выскочившими поверх других кнопок прямо перед нажатием?
Если мы тут говорим про то, во что компилируется Kotlin на примере компиляции в байткод JVM, то тут он как раз на одном уровне с Java — которая тоже компилируется в подобный же байткод, то есть у них там братские отношения, а не отец-сын, как у JavaScript->TypeScript.

Про стандартную библиотеку я говорил не в обвинение (вообще странно обвинять транспилер в том, что он транспилер) — а потому, что TypeScript наследует библиотеку от JavaScript (с элементами, который тот в свою очередь наследовал от ECMAScript), в смысле что он экспортирует те же самые типы. Kotlin типов Java не экспортирует сам по себе, у него собственные.

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


Про GO не могу сказать, но вот в Android подобное решается с помощью чего-то вроде вот такой обёртки для выхлопа достаточно простого парсера:
JSONObject


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

Просто бизнесу нужно показать Project Management Triangle и объяснить, что когда по условному проекту Х треугольник достигнет желаемых ими измерений, у конкурентов уже несколько лет будут
X + N >>> X.


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

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

Я, конечно, могу сам накидать примеров из любой кодовой базы, где из-за моды все стали писать list.stream().forEach(...) вместо любого другого варианта итерации, или бесконечные вопросы на StackOverflow, мол, "вот у меня работающий код, а как мне переписать всё на C++ Stream". Но тут масштаб не тот немного.


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

Kotlin/Java неправильно сравнивать с TypeScript/JavaScript.
TypeScript это надстройка над JavaScript, целиком наследует семантику и (если я правильно понимаю) не имеет собственной стандартной библиотеки.

Тогда как Kotlin — это самостоятельный язык, который, как и Java, компилируется в JVM-байткод… но может и не компилироваться, и ничего важного при этом, по сути, не потеряет.

Information

Rating
6,246-th
Location
Россия
Registered
Activity