Русский
Русский
English
Статистика
Реклама

Null

Null safety of Kotlin. Мысль про киллер фичу

02.06.2021 16:12:10 | Автор: admin

Познакомившись впервые с языком Котлин после продолжительной работы с Java меня воротило от одной мысли, что null-safety может быть полезен и вообще переменная без null - примитив, но я сам этого не осознавал.

Как это проявлялось:

  1. Не удобно работать с переменными и филдами, у которых не может быть null. Ну просто даже не понимал, как что-то не может быть null.

  2. Не удобно работать с nullable типами. Ну блин ?: !!

Вообщем шло время, потихоньку начинал привыкать к тому, что null может не существовать, даже пытался сделать что-то на мой тот взгляд идиоматичное: дефолтные значения в виде объекта.... Вообщем все это меня вгоняло в тоску и я очень хотел опять писать на Java, так как привык жить с null. Прошло время я уже нормально начал жить с котлиновским not nullable, как в друг в один прекрасный день я вернулся в Java... И через время осознал одну вещь:

Когда я работаю с легаси кодом (ну это весь код, который вообще пишется), то я не понимаю, где у может быть null, а где нет, что заставляет меня делать переключение контекста от реализации бизнес логики на выяснение: а может ли тут быть null?

Рассмотрим синтетику: У нас есть сущность номер телефона с филдами: номер, международный код города и префикс (ну +7, +3 и т.д.), написана она до нас, есть мэппинг в базу, вообщем все по канонам кровавого. По бизнесу все 3 поля номера телефона обязаны быть.

Если я в Java, то при работе с этой сущностью у меня есть сколько вариантов как ее использовать:

  1. Использовать как есть, не думая о том, что там с null-ам (продакшен разберется, если что баг пофиксим)

  2. Дойти до базы данных и посмотреть на констрейнт, тем самым убедившись, что null там не может быть и можно спокойно юзать эту сущность без проверок.

  3. Если расставлены аннотации @NotNull, нажать Ctrl+Q, чтобы увидеть описание.

  4. Обработать все поля, предполагая, что везде может быть null.

  5. Написать свой код, потом разобраться с потенциальным null.

  6. Можно еще придумать варианты....

Все, кроме первого варианта, заставляют меня приложить усилия на переключение контекста и отвлечься от основной мысли над которой я работаю, ну да пункт 5 может сохранить мое внимание какое-то время, но потом прийдется потратить еще кучу времени на переключение контекста. Первый в принципе не плох, но потом будет весело разбираться с NPE.

Какие у нас есть варианты в Котлин:

  1. Значение филда может быть null и я могу сразу обработать такой кейс.

  2. Значение не может быть null.

То есть оба варианта не сбивают меня, не заставляют меня переключить свое внимание.

Конечно тут можно возразить, что есть совместимость с Java и там все плохо в этом плане, на что есть такой ответ: совместимость это круто, но как он сделан, это трейд офф, чтобы не плодить море конструкций вида !! ?:

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

Подробнее..
Категории: Kotlin , Java , Null

Как можно и как нельзя использовать нулевой указатель в С

30.07.2020 16:13:15 | Автор: admin


Некоторым этот банальный вопрос уже набил оскомину, но мы взяли 7 примеров и попытались объяснить их поведение при помощи стандарта:


struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};/*1*/ *p;/*2*/ foo((*p, 5));                     /*3*/ A a{*p};/*4*/ p->data_mem;/*5*/ int b{p->data_mem};/*6*/ p->non_static_mem_fn();/*7*/ p->static_mem_fn();

Очевидная, но важная деталь: p, инициализированный нулевым указателем, не может указывать на объект типа А, потому что его значение отлично от значения любого указателя на объект типа А conv.ptr#1.


Disclaimer: статья содержит вольный перевод терминов и выдержек из стандарта на русский язык. Мы рекомендуем английскую версию статьи на dev.to, лишенную неточностей перевода.


Пример 1


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


*p;

Синтаксически это оператор выражения (expression statement, stmt.expr#1), в котором *p является выражением с отброшенным результатом, который, тем не менее, нужно вычислить. Определение унарного оператора * expr.unary.op#1 гласит, что этот оператор осуществляет косвенное обращение (indirection), и результатом является l-значение, которое обозначает объект или функцию, на которую указывает выражение. Его семантика понятна, чего не скажешь о том, должен ли объект существовать. Нулевой указатель в определении не упоминается ни разу.


Можно попробовать зацепиться за косвенное обращение, потому что есть basic.stc#4, в котором четко написано, что поведение при косвенном обращении через недопустимое значение указателя (indirection through an invalid pointer value) не определено. Но там же дается описание недопустимого значения указателя, под которое нулевой не подходит, и дается ссылка на basic.compound#3.4, где видно, что нулевой указатель и недопустимый это различные значения указателя.


Еще есть примечание в dcl.ref#5, которое гласит, что the only way to create such a reference would be to bind it to the object obtained by indirection through a null pointer, which causes undefined behavior, т.е. единственный способ создать такую ссылку привязать ее к объекту, полученному за счет косвенного обращения через нулевой указатель, что приводит к неопределенному поведению. Но придаточное в конце может относиться не только к косвенному обращению, но и к привязать (to bind), и в этом случае неопределенное поведение вызвано тем, что нулевой указатель не указывает на объект, о чем и говорится в основном тексте пункта dcl.ref#5.


Раз стандарт вместо однозначных формулировок оставляет пространство для интерпретаций в разрезе нашего вопроса, можно обратиться к списку дефектов языковой части стандарта, где Core Working Group среди прочего поясняет текст стандарта. Наш вопрос выделен в отдельный дефект, где CWG довольно давно пришла к неформальному консенсусу (так определен статус drafting), что неопределенное поведение влечет не разыменование само по себе, а конвертация результата разыменования из l-значения в r-значение. Если неформальный консенсус CWG звучит недостаточно весомо, то есть другой дефект, в котором рассматривается пример, аналогичный нашему примеру 7. Такой код назван корректным по этой же причине в официальной аргументации CWG.


В дальнейших рассуждениях мы будем опираться на этот консенсус. Если в будущем стандарт запретит разыменовывать нулевые указатели по примеру Си (N2176, 6.5.3.2 и сноска 104), значит, все примеры содержат неопределенное поведение, и на этом разговор можно закончить.


Пример 2


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


foo((*p, 5));  

Чтобы вызвать foo, требуется проинициализировать его параметр, для чего нужно вычислить результат оператора запятая. Его операнды вычисляются слева направо, причем все, кроме последнего, являются выражениями с отброшенным значением так же, как и в примере 1 (expr.comma#1). Следовательно, этот пример также корректен.


Пример 3


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


A a{*p};

Для инициализации a будет выбран неявный конструктор копирования, и для того, чтобы его вызвать, нужно проинициализировать параметр const A& допустимым объектом, в противном случае поведение не определено (dcl.ref#5). В нашем случае допустимого объекта нет.


Пример 4


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


p->data_mem;

Выражение этого оператора выражения при вычислении будет раскрыто в (*(p)).data_mem согласно expr.ref#2, которое обозначает (designate) соответствующий подобъект объекта, на который указывает выражение до точки (expr.ref#6.2). Параллели с примером 1 становятся особенно явными, если открыть, скажем, basic.lookup.qual#1, и увидеть, как to refer и to designate взаимозаменяемо используются в том же смысле, что и в expr.ref. Из чего мы делаем вывод, что это корректный код, однако некоторые компиляторы не согласны (см. про проверку константными выражениями в конце статьи).


Пример 5


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


int b{p->data_mem};

В продолжение предыдущего примера не будем отбрасывать результат, а проинициализируем им int. В этом случае результат нужно конвертировать в pr-значение, потому что выражения именно этой категории инициализируют объекты (basic.lval#1.2). Так как речь идет об int, будет осуществлен доступ к объекту результата (conv.lval#3.4), что в нашем случае ведет к неопределенному поведению, потому что ни одно из условий basic.lval#11 не соблюдается.


Пример 6


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


p->non_static_mem_fn();

class.mfct.non-static#1 гласит, что функции-члены разрешено вызывать для объекта типа, к которому они принадлежат (или унаследованного от него), или напрямую из определений функций-членов класса. Именно разрешено такой смысл вкладывается в глагол may be в директивах ИСО/МЭК, которым следуют все стандарты ИСО. Раз объекта нет, то и поведение при таком вызове не определено.


Пример 7


Открыть начало кода
struct A {    int data_mem;    void non_static_mem_fn() {}    static void static_mem_fn() {}};void foo(int) {}A* p{nullptr};


p->static_mem_fn();

Как говорилось в рассуждениях к примеру 1, Core Working Group считает этот код корректным. Добавить можно лишь то, что согласно сноске 59, выражение слева от оператора -> разыменовывается, даже его результат не требуется.


Проверка с помощью constexpr


Раз константные выражения не могут полагаться на неопределенное поведение (expr.const#5), то можно узнать мнение компиляторов о наших примерах. Пусть они и несовершенны, но как минимум нередко правы. Мы взяли три популярных компилятора, подправили пример под constexpr и для наглядности закомментировали те примеры, которые не компилируются, потому что сообщения об ошибках что у GCC, что у MSVC оставляют желать лучшего на данных примерах: godbolt.


Что получилось в итоге:


#
Код
Предположение
GCC 10.1
Clang 10
MSVC 19.24
1
*p;
+
+
+
+
2
foo((*p, 5));
+
+
+
+
3
A a{*p};
4
p->data_mem;
+
+
5
int b{p->data_mem};
6
p->non_static_mem_fn();
+
+
7
p->static_mem_fn();
+
+
+
+

Результаты заставляют несколько усомниться в выводе из примера 6 и в большей степени из примера 4. Но также интересно, что мы все сходимся во мнении о ключевом примере 1.


Спасибо, что остались с нами до конца, чтобы проследить за приключениями нулевого указателя в С++! :-) Обычно мы делимся на Хабре кусками кода из реальных проектов по разработке встроенного ПО для электроники, но этот раз нас заинтересовали чисто философские вопросы, поэтому примеры синтетические.


Если вы разделяете нашу любовь к противоречиям в С++, делитесь наболевшим в комментариях.

Подробнее..

Вы часто используете null? А он у нас в спецификации

09.02.2021 18:11:49 | Автор: admin

В нынешнее время для большинства разработчиков стали очевидными минусы использования null как возвращаемых типов или передачи их как аргумента.

Младшие разработчики даже если не понимают, то обычно следуют "чистому коду" (прочитав книжку Роберта Мартина). Поэтому код с возможностью возникновения NPE стал встречаться реже, хотя, конечно, они есть.

Не хочу сказать, что любое использование null - это плохо, скорее тут можно сказать "семь раз отмерь, один раз отрежь".

Тем не менее, я долгое время не жаловался насчет NPE, даже когда разработчики с языков у которых строгий контроль на эту тему хвастались своим null-safety. Но из-за одного бага я понял, что просто использование null это не так страшно, даже если вы возвращаете или передаете его. Разумеется, это очень плохо, но есть вещи хуже - null в спецификациях.

Было бы не особо интересно, если рассказ был об одной компании, которая сделала плохую спецификацию. Давайте вместо этого поговорим о более известной спецификации из Java EE - Java Servlet Specification, а конкретно возьмем класс HttpServletRequest и заглянем в метод getCookies()

getCookies

Cookie[] getCookies()

Returns an array containing all of the Cookie objects the client sent with this request. This method returns null if no cookies were sent.

  • Returns:

    an array of all the Cookies included with this request, or null if the request has no cookies

Больше всего следует обратить внимание на это:

This method returns null if no cookies were sent.

То есть, если здесь нет куков, то нужно вернуть null. Посмотрим на это со стороны разработчика:

  • Нам нужно вернуть массив из куки, а если их нет, то что-то, что означало бы, что куков в запросе не было.

В этом плане null выполняет свою роль, ведь кто как не он говорит, что значения там нет. Но тогда что же должен был означать пустой массив? Разве не то, что куков нет?

null vs empty array

В спецификации разработчики предпочли использовать null, что привело к некоторым последствиям, которые можно было бы избежать:

  • Усложнение API, которое заставляет пользователя каждый раз делать null-check

  • Запросы зачастую содержат хоть какой-нибудь куки, поэтому момент когда getCookies() возвратит null может произойти гораздо позже момента первого использования.

  • Усложнение имплементации этого метода. Если контейнер пустой, то нужно вернуть null (далее рассмотрим пример)

Но, конечно, не стоит считать что разработчики приняли такое решение напившись в баре. Использование null можно было оправдать, например, ради перформанса (избегаем аллоцирование пустого контейнера).

Тем не менее, это утверждение тоже можно поставить под сомнение.

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

  • Во-вторых, аллоцирования легко можно избежать просто создав пустой массив и возвращать его (например, какое-нибудь статическое неизменяемое поле)

Давайте посмотрим пару примеров, как использование null может испортить код

for (Cookie cookie : httpServletRequest.getCookies()) {   // NPE!  // }
int cookiesSize = httpServletRequest.getCookies().length    // NPE!

Добавляем null-check:

if (httpServletRequest.getCookies() != null)for (Cookie cookie : httpServletRequest.getCookies()) {    // }
Cookie[] cookies = httpServletRequest.getCookies();int cookiesSize = cookies == null ? 0 : cookies.length

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

Но усложнения касаются не только API, но и его имплементации. Рассмотрим как пример Jetty

Я не зря выбрал его, так как изначально он имел по сути неправильную реализацию или же лучше будет сказать имплементацию, которая не соответствует спецификации.

Коммит, который исправил ошибку

До:

return cookies == null?null:cookies.getCookies();

После:

if (cookies == null || cookies.getCookies().length == 0)  return null;return _cookies.getCookies();

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

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

Мы против!

Хотя люди, конечно же, возмущались новым изменениям, но не идти же против спецификации. Или же есть смельчаки, которые сделали это? Разумеется есть, хотя я и не знаю нарочно ли они это или по нутру хорошего кода изменили спецификацию?

Например, проект classpathx

The GNU Classpath Extensions project, aka classpathx builds free versions of Oracle's Java extension libraries, the packages in the javax namespace. It is a companion project of the GNU Classpath project.

У них есть скажем так "своя спецификация"

Cookie[] getCookies()

Gets all the Cookies present in the request.

  • Returns:

    an array containing all the Cookies or an empty array if there are no cookies

  • Since:

    2.0

Статические анализаторы

Не обойдем стороной и статические анализаторы. Они также считают что возвращать null не лучшее решение. Например, тот же Sonar, SEI CERT Oracle Coding Standart for Java

Заключение

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

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

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

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

Все что мы можем сейчас сделать - это вынести уроки из предыдущих ошибок (и своих и чужих):

  • Проблемы каких-то решений могут существовать и до того, как вы их осознаете

  • То что сейчас является обычной практикой в будущем может оказаться плохим кодом

  • Нужно делиться своими знаниями и опытом. Даже не ради кого-то другого, а ради себя, ведь возможно именно вашу статью прочитает будущий разработчик самого популярного фреймворка.

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


Speaking at a software conference in 2009, Tony Hoare apologized for inventing the null reference:[25]

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years

Подробнее..

SQL HowTo красивые отчеты по дырявым данным GROUPING SETS

28.07.2020 10:08:24 | Автор: admin
Для пользователя наш СБИС представляется единой системой управления бизнесом, но внутри состоит из множества взаимодействующих сервисов. И чем их становится больше тем выше вероятность возникновения каких-то неприятностей, которые необходимо вовремя отлавливать, исследовать и пресекать.

Поэтому, когда на каком-то из тысяч подконтрольных серверов случается аномальное потребление ресурсов (CPU, памяти, диска, сети, ...), возникает потребность разобраться кто виноват, и что делать.


Для оперативного мониторинга использования ресурсов Linux-сервера в моменте существует утилита pidstat. То есть если пики нагрузки периодичны их можно высидеть прямо в консоли. Но мы-то хотим эти данные анализировать постфактум, пытаясь найти процесс, создавший максимальную нагрузку на ресурсы.

То есть хочется иметь возможность смотреть по ранее собранным данным разные красивые отчеты с группировкой и детализацией на интервале типа таких:



В этой статье рассмотрим, как все это можно экономично расположить в БД, и как максимально эффективно собрать по этим данным отчет с помощью оконных функций и GROUPING SETS.

Сначала посмотрим, что за данные мы можем извлечь, если брать все по максимуму:

pidstat -rudw -lh 1
Time UID PID %usr %system %guest %CPU CPU minflt/s majflt/s VSZ RSS %MEM kB_rd/s kB_wr/s kB_ccwr/s cswch/s nvcswch/s Command
1594893415 0 1 0.00 13.08 0.00 13.08 52 0.00 0.00 197312 8512 0.00 0.00 0.00 0.00 0.00 7.48 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
1594893415 0 9 0.00 0.93 0.00 0.93 40 0.00 0.00 0 0 0.00 0.00 0.00 0.00 350.47 0.00 rcu_sched
1594893415 0 13 0.00 0.00 0.00 0.00 1 0.00 0.00 0 0 0.00 0.00 0.00 0.00 1.87 0.00 migration/11.87

Все эти значения делятся на несколько классов. Некоторые из них меняются постоянно (активность CPU и диска), другие редко (выделение памяти), а Command не только редко меняется в рамках одного процесса, но еще и регулярно повторяется на разных PID.

Структура базы


Для простоты давайте ограничимся одной метрикой каждого класса, которые мы будем сохранять: %CPU, RSS и Command.

Раз мы заведомо знаем, что Command регулярно повторяется просто вынесем его в отдельную таблицу-словарь, где UUID-ключом будет выступать MD5-хэш:

CREATE TABLE diccmd(  cmd    uuid      PRIMARY KEY, data    varchar);

А для самих данных нам подойдет таблица такого вида:

CREATE TABLE pidstat(  host    uuid, tm    integer, pid    integer, cpu    smallint, rss    bigint, cmd    uuid);

Обращу внимание, что раз %CPU приходит к нам всегда с точностью 2 знаков после запятой и заведомо не превышает 100.00, то мы спокойно можем домножить его на 100 и положить в smallint. С одной стороны, это избавит нас от проблем точности учета при операциях, с другой все-таки лучше хранить только 2 байта по сравнению с 4 байтами real или 8 байтами double precision.
Подробнее о способах эффективной упаковки записей в PostgreSQL-хранилище можно прочитать в статье Экономим копеечку на больших объемах, а про увеличение пропускной способности базы на запись в Пишем на субсветовой: 1 host, 1 day, 1TB.

Бесплатное хранение NULL'ов


Чтобы сэкономить производительность дисковой подсистемы нашей базы и занимаемый базой объем, постараемся как можно больше данных представить в виде NULL их хранение практически бесплатно, поскольку занимает лишь бит в заголовке записи.
Подробнее с внутренней механикой представления записей в PostgreSQL можно ознакомиться в докладе Николая Шаплова на PGConf.Russia 2016 Что у него внутри: хранение данных на низком уровне. Конкретно хранению NULL посвящен слайд #16.
Снова внимательно посмотрим на виды наших данных:

  • CPU/DSK
    Меняется постоянно, но очень часто обращается в ноль так что выгодно писать в базу NULL вместо 0.
  • RSS/CMD
    Меняется достаточно редко поэтому будем писать NULL вместо повторов в рамках одного и того же PID.

Получается картинка вроде такой, если смотреть на нее в разрезе конкретного PID:



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

То есть у записи с заполненным значением CMD заполнено и значение RSS. Запомним этот момент, он нам еще пригодится.

Собираем красивый отчет


Давайте теперь соберем запрос, который покажет нам потребителей ресурсов конкретного хоста на определенном временном интервале.

Но сделаем это сразу с минимальным использованием ресурсов примерно как в статье про SELF JOIN и оконные функции.

Использование входящих параметров


Чтобы не указывать значения параметров отчета (или $1/$2) в нескольких местах по ходу SQL-запроса, выделим CTE из единственного json-поля, в котором по ключам находятся эти самые параметры:

-- сохраняем параметры отчетаWITH args AS (  SELECT    json_object(      ARRAY[        'dtb'      , extract('epoch' from '2020-07-16 10:00'::timestamp(0)) -- переводим timestamp в integer      , 'dte'      , extract('epoch' from '2020-07-16 10:01'::timestamp(0))      , 'host'      , 'e828a54d-7e8a-43dd-b213-30c3201a6d8e' -- это у нас uuid      ]::text[]    ))

Извлекаем сырые данные


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

CREATE INDEX ON pidstat(host, tm);

-- извлекаем "сырые" данные, src AS (  SELECT    *  FROM    pidstat  WHERE    host = ((TABLE args) ->> 'host')::uuid AND    tm >= ((TABLE args) ->> 'dtb')::integer AND    tm <  ((TABLE args) ->> 'dte')::integer)

Группировка по ключу анализа


Для каждого найденного PID определим интервал его активности и возьмем CMD с первой записи на этом интервале.



Для этого воспользуемся уникализацией через DISTINCT ON и оконными функциями:

-- группировка по ключу анализа, pidtm AS (  SELECT DISTINCT ON(pid)    host  , pid  , cmd  , min(tm) OVER(w) tmb -- начало активности процесса на интервале  , max(tm) OVER(w) tme -- завершение активности  FROM    src  WINDOW    w AS(PARTITION BY pid)  ORDER BY    pid  , tm)

Границы активности процесса


Заметим, что относительно начала нашего интервала первой попавшейся записью может оказаться как та, которая уже имеет заполненное поле CMD (PID#1 на картинке выше), так и с NULL'ом, обозначающим продолжение заполненного выше по хронологии значения (PID#2).

Те из PID, которые остались без CMD в результате предыдущей операции, начались раньше начала нашего интервала значит, эти начала надо найти:



Поскольку мы точно знаем, что очередной сегмент активности начинается с заполненного значения CMD (а там и заполненный RSS, значит), тут нам поможет условный индекс:

CREATE INDEX ON pidstat(host, pid, tm DESC) WHERE cmd IS NOT NULL;

-- определяем начало активности каждого "неопределившегося" процесса, precmd AS (  SELECT    t.host  , t.pid  , c.tm  , c.rss  , c.cmd  FROM    pidtm t  , LATERAL(      SELECT        *      FROM        pidstat -- увы, SELF JOIN не избежать      WHERE        (host, pid) = (t.host, t.pid) AND        tm < t.tmb AND        cmd IS NOT NULL -- садимся на условный индекс      ORDER BY        tm DESC      LIMIT 1  ) c  WHERE    t.cmd IS NULL -- только для "неопределившихся")

Если мы хотим (а мы хотим) знать время окончания активности сегмента, то уже для каждого PID придется воспользоваться двухходовкой для определения нижней границы.
Аналогичную методику мы уже использовали в статье PostgreSQL Antipatterns: навигация по реестру.



-- определяем момент окончания активности сегмента, pstcmd AS (  SELECT    host  , pid  , c.tm  , NULL::bigint rss  , NULL::uuid cmd  FROM    pidtm t  , LATERAL(      SELECT        tm      FROM        pidstat      WHERE        (host, pid) = (t.host, t.pid) AND        tm > t.tme AND        tm < coalesce((          SELECT            tm          FROM            pidstat          WHERE            (host, pid) = (t.host, t.pid) AND            tm > t.tme AND            cmd IS NOT NULL          ORDER BY            tm          LIMIT 1        ), x'7fffffff'::integer) -- MAX_INT4      ORDER BY        tm DESC      LIMIT 1  ) c)

JSON-конвертация форматов записей


Замечу, что мы отбирали в precmd/pstcmd только те поля, которые влияют на последующие строки, а всякие CPU/DSK, которые меняются постоянно нет. Поэтому формат записей в исходной таблице и этих CTE у нас расходится. Не беда!

  • row_to_json превращаем каждую запись с полями в json-объект
  • array_agg собираем все записи в '{...}'::json[]
  • array_to_json преобразуем массив-из-JSON в JSON-массив '[...]'::json
  • json_populate_recordset генерируем из JSON-массива выборку заданной структуры

Тут мы используем именно однократный вызов json_populate_recordset вместо множественного json_populate_record, потому что это банально быстрее в разы.
Склеиваем найденные начала и концы в общую кучу и добавляем к исходному набору записей:

-- склеиваем все, uni AS (  TABLE srcUNION ALL  SELECT    *  FROM    json_populate_recordset( -- развернули в полный      NULL::pidstat    , (        SELECT          array_to_json(array_agg(row_to_json(t))) -- свернули сокращенный формат        FROM          (            TABLE precmd          UNION ALL            TABLE pstcmd          ) t      )    ))


Заполняем NULL-пропуски повторов

Воспользуемся моделью, рассмотренной в статье SQL HowTo: собираем цепочки с помощью window functions.
Сначала выделим группы повторов:

-- выделение групп, grp AS (  SELECT    *  , count(*) FILTER(WHERE cmd IS NOT NULL) OVER(w) grp  -- группы по CMD  , count(*) FILTER(WHERE rss IS NOT NULL) OVER(w) grpm -- группы по RSS  FROM    uni  WINDOW    w AS(PARTITION BY pid ORDER BY tm))

Причем по CMD и RSS группы будут независимы друг от друга, поэтому могут выглядеть примерно так:



Заполним пропуски по RSS и посчитаем продолжительность каждого отрезка, чтобы корректно учесть распределение нагрузки по времени:

-- заполняем пропуски, rst AS (  SELECT    *  , CASE      WHEN least(coalesce(lead(tm) OVER(w) - 1, tm), ((TABLE args) ->> 'dte')::integer - 1) >= greatest(tm, ((TABLE args) ->> 'dtb')::integer) THEN        least(coalesce(lead(tm) OVER(w) - 1, tm), ((TABLE args) ->> 'dte')::integer - 1) - greatest(tm, ((TABLE args) ->> 'dtb')::integer) + 1    END gln -- продолжительность сегмента от предыдущей записи или начала интервала  , first_value(rss) OVER(PARTITION BY pid, grpm ORDER BY tm) _rss -- заполнение пропусков по RSS  FROM    grp  WINDOW    w AS(PARTITION BY pid, grp ORDER BY tm))



Мультигруппировка с помощью GROUPING SETS


Поскольку мы хотим увидеть в результате одновременно и сводную информацию по всему процессу, и его детализацию по разным сегментам активности, воспользуемся группировкой сразу по нескольким наборам ключей с помощью GROUPING SETS:

-- мультигруппировка, gs AS (  SELECT    pid  , grp  , max(grp) qty -- количество сегментов активности по PID  , (array_agg(cmd ORDER BY tm) FILTER(WHERE cmd IS NOT NULL))[1] cmd -- "должен остаться только один"  , sum(cpu) cpu  , avg(_rss)::bigint rss  , min(tm) tmb  , max(tm) tme  , sum(gln) gln  FROM    rst  GROUP BY    GROUPING SETS((pid, grp), pid))


Вариант использования (array_agg(... ORDER BY ..) FILTER(WHERE ...))[1] позволяет нам прямо при группировке, без дополнительных телодвижений получить первое непустое (даже если оно не самое первое) значение из всего набора.
Вариант получения сразу нескольких разрезов целевой выборки очень удобен для формирования различных отчетов с детализацией, чтобы все детализирующие данные не надо было перестраивать, а чтобы в UI они попадали вместе с основной выборкой.

Словарь вместо JOIN


Создаем словарь CMD для всех найденных сегментов:
Подробнее про методику ословаривания можно прочесть в статье PostgreSQL Antipatterns: ударим словарем по тяжелому JOIN.

-- словарь CMD, cmdhs AS (  SELECT    json_object(      array_agg(cmd)::text[]    , array_agg(data)    )  FROM    diccmd  WHERE    cmd = ANY(ARRAY(      SELECT DISTINCT        cmd      FROM        gs      WHERE        cmd IS NOT NULL    )))

А теперь используем его вместо JOIN, получая финальные красивые данные:

SELECT  pid, grp, CASE    WHEN grp IS NOT NULL THEN -- это "сегмент" активности      cmd  END cmd, (nullif(cpu::numeric / gln, 0))::numeric(32,2) cpu -- приводим CPU к "средней" нагрузке, nullif(rss, 0) rss, tmb -- верхняя граница активности, tme -- нижняя граница активности, gln -- продолжительность активности, CASE    WHEN grp IS NULL THEN -- это весь процесс      qty  END cnt, CASE    WHEN grp IS NOT NULL THEN      (TABLE cmdhs) ->> cmd::text -- извлекаем данные из словаря  END commandFROM  gsWHERE  grp IS NOT NULL OR -- это запись "сегмента"  qty > 1 -- или в процессе больше одного сегментаORDER BY  pid DESC, grp NULLS FIRST;



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


[посмотреть на explain.tensor.ru]

Всего 44ms и 33MB данных прочитано!
Подробнее..

Из песочницы Null safety в Dart

02.08.2020 14:11:42 | Автор: admin
Привет, Хабр! Представляю вашему вниманию перевод статьи Announcing sound null safety автора Filip Hracek с моими комментариями:

Null safety безопасная работа с пустыми ссылками. Далее по тексту для краткости и по причине устойчивости термина будет использоваться английское наименование null, null safety. Да и перевод нулевая безопасность наводит совсем на противоположные мысли.
sound в данном контексте (sound null safety) можно перевести как надежный.
Если есть предложения по улучшению перевода или нашли ошибки пишите в личку, постараемся исправиться.
Наступил важный этап для команды Dart с их представлением технического превью наработок по null safety. Null safety позволяет избежать целого класса ошибок, которые часто трудно обнаружить, а в качестве бонуса обеспечивает ряд улучшений производительности. На данный момент мы выпустили предварительное техническое превью и ждем от вас обратной связи.

В этой статье мы раскроем планы команды Dart по развертыванию null safety, а также объясним, что скрывается за термином Sound null safety, и чем этот подход отличается от других языков программирования.
Описываемая версия была представлена 10-го июня 2020 года.

Зачем нужна null safety?


Dart типобезопасный (type-safe) язык. Это означает, что когда вы получаете переменную некоторого типа, компилятор может гарантировать, что она принадлежит ему. Но безопасность типов сама по себе не гарантирует, что переменная не равна null.

Null-ошибки встречаются часто. Поиск на GitHub находит тысячи репортов (issue), вызванных нулевыми значениями в Dart-коде, и еще больше коммитов, пытающихся решить эти проблемы.

Попробуйте обнаружить проблему обнуления ссылки в следующем примере:

void printLengths(List<File> files) {  for (var file in files) {    print(file.lengthSync());  }}

Эта функция, безусловно, завершится ошибкой, если вызывается с обнуленным параметром, но есть и второй случай, который следует рассмотреть:

void main() {  // Error case 1: passing a null to files.  printLengths(null);   // Error case 2: passing list of files, containing a null item.  printLengths([File('filename1'), File('filename2'), null]);}

Null safety устраняет эту проблему:

image

С null safety вы можете с большей уверенностью опираться на свой код. Не будет больше надоедливых ошибок обращения к обнуленной переменной во время выполнения. Только статические ошибки в момент компиляции кода.
Если быть совсем честными, то текущая реализация все же оставляет несколько возможностей словить null-ошибки в момент выполнения, о них чуть позже.

Sound (надежная) null safety


Реализация null safety в языке Dart надежна (sound). Если разбирать на приведенном выше примере, то это означает, что компилятор Dart на 100% уверен, что массив файлов и элементы в нем не могут быть нулевыми. Когда компилятор Dart проанализирует ваш код и определит, что переменная не имеет значения null, то эта переменная всегда будет иметь значение: если вы проверите свой исполняемый код в отладчике, вы увидите, что возможность обнуления попросту отсутствует во время выполнения. Существуют реализации, не являющиеся надежными, в которых все еще необходимо выполнять проверки наличия значения в момент выполнения. В отличие от прочих языков, Dart разделяет надежность реализации с языком Swift.
Немного спорное заявление, которое заставило меня копнуть в эту тему немного глубже, чтобы понять, как реализуется null safety в разных языках. В итоге я сравнил эти реализации в Swift, Kotlin и Dart. Результат этих изысканий можно посмотреть на записи нашего внутрикомандного доклада.
Подобная надежная реализация null safety в Dart имеет еще одно приятное следствие: это означает, что ваши программы могут быть меньше и быстрее. Поскольку Dart действительно уверен, что переменные никогда не могут быть обнулены, Dart может оптимизировать результат компиляции. Например, AOT-компилятор может создавать меньший и более быстрый нативный код, поскольку ему не нужно добавлять проверки на пустые ссылки.

Мы видели некоторые очень многообещающие предварительные результаты. Например, мы увидели улучшение производительности на 19% в микробенчмарке, который эмулирует типичные шаблоны рендеринга в инфраструктуре Flutter.

Основные принципы


Прежде чем приступить к детальному проектированию null safety, команда Dart определила три основных принципа:

Необнуляемость по-умолчанию. /** Часто можно увидеть в виде абревиатуры NNBD в документации **/ Если вы явно не скажете Dartу, что переменная может быть обнулена, он сочтет ее необнуляемой. Мы выбрали это как значение по умолчанию, потому что обнаружили, что в API ненулевое значение на текущий момент является наиболее распространенным. /** Вероятно, речь идет о переработке текущего API Flutter **/.

Поэтапная применимость. Мы понимаем, что должна существовать возможность постепенного перехода к null safety шаг за шагом. На самом деле, нужно иметь обнуляемый и null safety код в одном проекте. Для этого мы планируем предоставить инструменты, помогающие с миграцией кода.

Полная надежность (sound). Как упоминалось выше, null safety в Dart надежна. Как только вы преобразуете весь свой проект и ваши зависимости на использование null safety, вы получите все преимущества надежности.

Объявление переменных с null safety


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

// In null-safe Dart, none of these can ever be null.var i = 42;final b = Foo();String m = '';

Dart позаботится о том, чтобы вы никогда не присваивали значение null ни одной из перечисленных выше переменных. Если вы попытаетесь выполнить i = null даже тысячу строк спустя, вы получите ошибку статического анализа и красные волнистые линии ваша программа откажется компилироваться.

Если вы хотите, чтобы ваша переменная могла обнуляться, вы можете использовать '?' вот так:

// These are all nullable variables.int? j = 1;  // Can be null later.final Foo? c = getFoo();  // Maybe the function returns null.String? n;  // Is null at first. Can be null at any later time, too

Перечисленные выше переменные ведут себя точно так же, как и все переменные в актуальной версии Dart.

'?'' можно также использовать в прочих местах:

// In function parameters.void boogie(int? count) {  // It's possible that count is null.}// In function return values.Foo? getFoo() {  // Can return null instead of Foo.}// Also: generics, typedefs, type checks, etc.// And any combination of the above.

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

Упрощение использования null safety


Команда Dart изо всех сил старается сделать null safety максимально простой в использовании. Например, посмотрите на этот код, который использует if, чтобы проверить нулевое значение:

void honk(int? loudness) {  if (loudness == null) {    // No loudness specified, notify the developer    // with maximum loudness.    _playSound('error.wav', volume: 11);    return;  }  // Loudness is non-null, let's just clamp it to acceptable levels.  _playSound('honk.wav', volume: loudness.clamp(0, 11));}

Обратите внимание, что Dart достаточно умен, чтобы понять, что к тому времени, когда мы пройдем оператор if, переменная loudness не может иметь значение null. И поэтому Dart позволяет нам вызывать метод clamp() без лишних танцев с бубном. Это удобство обеспечивается так называемым анализом потока выполнения (flow analysis): анализатор Dart просматривает ваш код, как если бы он выполнял его, автоматически выясняя дополнительную информацию о вашем коде.
Flow analysis, уже существующая фича языка Dart, используется, например, при проверке соответствия типа. В данном случае они переиспользовали ее для null safety, что позволяет смотреть на отношения типа и опционального типа как на наследование:
foo(dynamic str) {  if (str is String) {    //У dynamic нет метода length, но компилятор     //понимает из контекста что это тип String    print(str.length);    }}

Вот еще один пример, когда Dart может быть уверен, что переменная не равна null, так как мы всегда присваиваем ей значение:
int sign(int x) {  // The result is non-nullable.  int result;  if (x >= 0) {    result = 1;  } else {    result = -1;  }  // By this point, Dart knows the result cannot be null.  return result;}

Если вы удалите какое-либо из перечисленных выше присвоений (например, удалив строку result = -1;), Dart не сможет гарантировать, что result будет иметь значение вы получите статическую ошибку, и ваш код не скомпилируется.
Анализ потока выполнения работает только внутри функций. Если у вас есть глобальная переменная или поле класса, то Dart не может гарантировать, что ему будет присвоено значение. Dart не может моделировать поток выполнения всего вашего приложения. По этой причине вы можете использовать новое ключевое слово late, если знаете, что переменная будет инициализирована на момент первого к ней обращения, но не можете инициализировать ее в момент объявления.

class Goo {  late Viscosity v;   Goo(Material m) {    v = m.computeViscosity();  }}

Обратите внимание, что v не может быть обнулена, хотя изначально не имеет значения. Dart считает, что вы не будете пытаться прочитать v, пока ему не будет присвоено ненулевое значение, и ваш код компилируется без ошибок.
А вот и он момент для выстрела себе в ногу.

Опасный костыль, который будет главным признаком пахнущего кода. Ситуацию усугубляет еще и то, что нет возможности проверить состояние переменной. Команда Dart обсуждала такую возможность, но пришла к выводу, что на данном этапе проще не реализовывать проверки, как, например, в Kotlin через рефлексию.

Второе спорное решение это интеграция знакомого всем Swift-программистам восклицательного знака, то есть, теперь можно будет делать force unwrap в Dart.
void main() {  String? t;  print(t!.length);}

В обоих случаях (с late и !) мы сможем при неправильном использовании получить ошибку в момент выполнения программы.

Также стоит упомянуть в контексте late еще одно нововведение, которое почему-то скрывается в статьях и видео от команды Dart. Добавлено новое ключевое слово required для именованных параметров конструктора. Это уже знакомый @required, только не из отдельного пакета и без собачки.
class Temp {  String str;  Temp({required this.str});    //ну или как альтернатива  Temp.alt({strAtr}) : this.str = strAtr;}

Обратная совместимость


Команда Dart работала больше года, чтобы довести null safety до уровня технического превью. Это самое большое изменение языка со времен релиза второй версии. Тем не менее, это изменение, не ломающее обратную совместимость. Существующий код может вызывать код с null safety и наоборот. Даже после полноценного релиза null safety будет дополнительной опцией, которую вы сможете использовать, когда будете готовы. Ваш существующий код продолжит работать без изменений.

На днях базовые библиотеки Dart были обновлены с использованием null safety. В качестве показательного примера обратной совместимости замена существующих базовых библиотек прошла без единого проваленного теста и без ошибок в тестовых приложениях, работающих на тестовых средах Dart и Flutter. Даже обновление базовых библиотек для множества внутренних клиентов Google прошло без сучка и задоринки. Мы планируем переделать все наши пакеты (packages) и приложения на использование null safety после релиза, надеемся, вы поступите также. Но вы можете делать это в своем темпе, пакет за пакетом, приложение за приложением.
Эти бы слова, да разработчикам Swift в уши, особенно 3-й версии
Но даже тут не так все радужно, разработчики сами говорят, что при совмещении null safety кода и старого кода в одном проекте они не могут гарантировать надежность (soundness) системы типов.

Дальнейший план действий


Мы планируем развернуть null safety постепенно в три этапа:

  1. Техническое превью. Запустилось в момент выхода оригинала статьи (10.06.2020) и доступно на dev-ветке. Стоит обратить внимание на раздел Начни уже сейчас. Все еще очень нестабильно и может поменяться, так что пока не рекомендуем использовать ее в продакшн коде. Но мы будем рады, если вы попробуете и дадите нам обратную связь!
  2. Beta-версия. Null safety станет доступна на beta-ветке Dart и больше не будет скрываться за экспериментальным флагом. Реализация будет близка к ожидаемой финальной версии. Можно будет начать миграцию ваших пакетов и плагинов на pub.dev, но пока не рекомендуется публиковать данное изменение как стабильную версию.
  3. Стабильная версия. Null safety будет доступна для всех. Вам будет предложено опубликовать ваши обновленные пакеты и плагины как стабильные версии. На этом этапе также стоит мигрировать ваши приложения.

Если все пойдет по плану, мы выпустим стабильную версию Dart с null safety до конца года. Время от времени мы будем добавлять инструменты, которые помогут перейти на null safety. Среди них:

  • Инструмент миграции, помогающий автоматизировать многие этапы обновления существующих пакетов и приложений;
  • Теги на pub.dev, помечающие пакеты с поддержкой null safety;
  • Расширение команды 'pub outdated' с поддержкой поиска последних версий ваших зависимостей, поддерживающих null safety.

Начни уже сейчас


Самый быстрый способ попробовать null safety уже сегодня это nullsafety.dartpad.dev версия DartPad с включенной функцией null safety. Откройте раскрывающийся список Learn with Snippets, чтобы найти серию обучающих упражнений, описывающих новый синтаксис и основы null safety.

image

Также можно поэкспериментировать с null safety в небольших консольных приложениях (мы еще не обновили более крупные фреймворки, такие как Flutter). Для начала нужно загрузить Dart SDK с dev-ветки, затем можно скачать этот пример консольного приложения. В README файле есть инструкции по запуску приложения с включением экспериментальной функции null safety. Другие файлы в примере предоставляют конфигурации запуска, которые позволят выполнять отладку в VS Code и Android Studio.

Также можно ознакомиться с документацией (в дальнейшем появится еще):


Мы очень рады возможности реализовать null safety в Dart. Надежная безопасная работа с пустыми ссылками станет отличительной чертой Dart, которая поможет писать наиболее надежный и производительный код. Мы надеемся, что вы найдете время поэкспериментировать с текущей версией null safety и оставите свой отзыв в нашем баг-трекере. Хорошего кода!
Спасибо за то, что дочитали до конца. Перевод немного запоздал, но, надеюсь, комментарии привнесли пользы материалу и это не стало просто переводом один в один. От себя хочется добавить, что эта фича действительно полезный шаг вперед для всей экосистемы Flutter. Жду с нетерпением, чтобы начать использовать уже на живых приложениях. А пока, как говорится на забугорском, stay tuned!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru