Допустим, программист уложил необходимую работу в 150 строк кода. Что улучшится от того, что он разобьёт этот код на пять функций по тридцать строк?
Пусть первая часть функции считает, чему равны foo и buka. Вторая — чему равны bar и zuka. Третья использует foo и bar, четвёртая — buka и zuka. Пятая (вы говорили про пять) их как-то обобщает, заодно логгируя.
Чтобы грок (на всякий случай — осознать во всех деталях) всю 150-строчную функцию, читатель должен, в частности, убедиться, что вторая часть не меняет значения foo и buka, а третья — buka и zuka. А то мало ли, что происходит на 4-м уровне вложенности цикла в этих частях и под каким хитрым условием? Защиты ведь от этого нет, нельзя временно навесить const на переменную, а потом его снять. А в случае раздельных функций это выполняется примитивно и прямолинейно — foo и buka просто принимаются как результат 1-й подфункции (неважно, как часть возвращаемого значения, по указателю, по ссылке, как-то ещё) и не передаются во 2-ю, а в 3-ю и 4-ю передаются только по значению (копируются).
Вы можете сказать, что можно их назначить const и они будут таковыми до конца жизни функции. Да, но не в Ruby (в нём нет таких констант), и не в чуть-чуть более сложном случае (пусть эти 5 частей выполняются ещё раз в цикле… не принять изменение из отдельной функции — банально, не разрешить менять — уже серьёзные затраты на изучение каждый раз).
Это был первый аспект. Второй — обеспечение доверия к качеству реализации через тестирование. Про тестирование я уже высказался, повторю чуть иными словами: функция в таком виде слишком неудобна для автоматического тестирования. Нет возможности получить промежуточные результаты, неудобно подавать разные входные данные и контролировать результаты каждого этапа. Для динамически типизированного языка такое тестирование критично; большинство возможных проблем, типа опечаток в именовании полей мап, могло бы быть выловлено в языке типа C++ на стадии компиляции, здесь же этого нет и вся надежда на тесты.
Ну так и линейный код тоже может использовать локальные переменные внутри фигурных скобок.
Но там нет возможности запретить видимость переменных внешнего блока. Ни в одном языке программирования, насколько я видел, такого нет; такой запрет делается уже структурированием на функции/процедуры/назови-как-хочешь. И тем более этого нет в Ruby.
Вы можете вспомнить про лямбды в C++, но они фактически отдельные вложенные функции.
Хм-м-м… А может, надо поднимать планку и не пускать в профессию слабых программистов?
Невыгодно. Если требовать от программистов абсолютно высокого внимания ко всем деталям, то придётся выкинуть 99% тех, кто сейчас успешно работает. Повторюсь — в отрасли останутся аутисты и безумные гениальные счётчики в уме. Вы тоже будете выкинуты на мороз, аутисты не пишут такие комментарии на хабр :)
И что делать, когда пароль надо сменить — например, сайт взломали и пароли утекли?
Специфичный алгоритм для каждого сайта? Это ещё сложнее для запоминания.
Функция composite_cut в примере безусловно плоха, но описание того, чем именно она плоха, не ложится на понятия отличников или троечников. Её проблемы не в количестве сущностей — там как раз нормальное структурирование. Сложно не понять, что она делает. Сложно это доказать, хотя бы самому себе. Суть проблемы — она попросту нетестируема. Как бы автор или читатель не был уверен в корректности, тесты для такой задачи необходимы — например, чтобы подтвердить соответствие нижележащего API ожиданиям, воплощённым в коде, или просто отловить банальные опечатки, на которые «глаз замыливается» даже у лучшего отличника. Здесь имеем несколько этапов, результаты которых используются для следующих этапов, но проверить их по выходным данным сложно или просто нельзя. Эту тему хорошо отработал, например, Майкл Физерс, вместе с методами решения, не буду повторяться, кроме того, что её надо было именно для этого разрезать на несколько независимых частей.
Точно так же, необходимость следить при визуальной верификации за тем, что результаты предыдущих этапов не портятся до их использования в результате коллизий имён, сильно усложняет визуальную верификацию. (Программную — не рассматриваю, в силу её редкости и дороговизны, но визуальную — вплоть до простого код-ревью — необходимо учитывать и способствовать ей.) Тут ещё и не было необходимости, как мне кажется, экономить на вызовах подфункций.
Как отличники, так и троечники могут обладать высокой внимательностью к деталям, а могут не замечать на ровном месте ошибки типа anspk вместо anpsk (пример реальный с точностью до букв — я с таким боролся в старом фортрановском коде). В крупных неразделённых блоках кода гарантированно следить за целостностью (через длительное развитие и изменение) могут только люди с определёнными классами отклонений (например, аутизм). Таких, увы, сложно найти, большая редкость — чтобы ещё и программировали; но если и найти — их невыгодно использовать из пушки по воробьям.
Ключевое слово было — _того_ Бейсика. А не современного в Офисе. Я не понимаю, как и зачем можно было прочитать иначе.
> Вы просто не знаете, где используется бейсик.
Вы просто домысливаете то, что не писалось. Ну и уводите в бессмысленный оффтопик.
Ну и вызвать желание хотя бы попробовать — становится сложнее. Уровень того Бейсика — в лучшем случае примитивная графика, а скорее текст — сейчас мало кому интересен, когда вокруг столько готовых разноцветных игр с суперграфикой :(
А ведь для толкового специалиста невозможно не проходить через этот «нецветной и ненаглядный» этап.
У меня был транковый телефон, с 1998 где-то до 2000. Перепрошитый Kenwood TK 378, база (не помню фирму — оператора) на «Карандаше» (телецентр на Мельникова).
За абонплату допускалось неограниченное количество звонков на город, но по 3 минуты на каждый, дальше принудительно рвалось.
На базе отдыха на Козинке было популярным средством :)
чтобы прочитать индекс размером N байт, потребуется прочитать 10*N байт с диска, т.к. данные лежат очень неплотно
Это нужно какие-то спецметоды разрежения применять, чтобы такое получить. В подавляющем большинстве реализаций B, B⁺-деревьев производится оптимизация на заполнение каждой страницы индекса не менее чем на 1/2, 2/3.
Можете указать точный источник страшилки про "10*N"?
Не упомянут ещё один вариант пометки — применявшийся для классического lint: /* FALLTHROUGH */ или /* FALLTHRU */. Во многих исходниках на C/C++ он используется до сих пор. Там принимается ещё десяток опций — проверка стиля printf, использование аргументов и т.п. К сожалению, новые анализаторы не используют эти директивы (не знаю ни одного), а lint не умеет C++ или C99, поэтому такие директивы в новом коде не пишут — но ещё полно старого.
Рекомендую определять их наравне с новыми средствами в духе этого C++17.
Это только на один цикл вглубь. На следующем окажется, что все элементы одинаковы.
(Хотя, чтобы корректно выйти из этой ситуации, исходный алгоритм должен делить вход не на две части, а на три, или же пополамить набор элементов, равных опорному. Для quicksort некоторые методы деления отрезка специально заточены на оптимизацию такого варианта. Это можно перенести и на quickselect, но явно.)
Автовывод типа (как в var) вообще-то в языках программирования с самого их рождения. Когда вы видите выражение типа A+B, вы не пишете его тип на каждую операцию. Когда вы зовёте f(A+B), вы не пишете тип фактического аргумента функции (и можете нарваться на автоконверсию). Если вы вообще способны работать с языком, в котором есть подобные способности автовыведения типа — то есть со 100% нынешних языков — то в чём разница с тем, что это промежуточное значение получило название (формально, присвоено переменной)?
Точно так же, вы или доверяете транслятору и способностям кодера оценить тип выражения, или не доверяете. Если не доверяете, вы не будете использовать автовывод (var в Java, C#, auto в C++...) Если доверяете — вам автовывод не помешает. Вы, как видно, не доверяете. Ну, тоже вариант. Но хотелось бы понять причины такого недоверия.
Я в общем-то тоже в основном не доверяю :), но auto (C++) в итераторах — очевидно удобная возможность, не мешающая правильному использованию. Здесь будет что-то подобное.
А заабьюзить можно что угодно. Самый дурной вариант, мне кажется, это автосужение множества значений — например, при var автовывод дал int, а затем ему пытаются присвоить float и не предполагают возможность усечения до целого. Вот такое должно быть явно прописано в списке подводных камней.
Установленное изначально как 16.04.{2,3} получает HWE-ветку ядер (4.10, 4.13), если не ставилось явно как сервер.
Установленное как 16.04, 16.04.1, будет иметь не-HWE ветку (4.4) до тех пор, пока админ явно не поставит HWE ядра.
Это краткий пересказ убунтовского wiki.
Про апгрейд сложно сказать однозначно, но мои переведённые с 14.04 системы имеют не-HWE ядра.
PS: а можно и явно ставить ядра более поздних лини через соответствующие generic-пакеты.
Может, и проще. Зависит всё-таки от размера скрипта и сложности логики в нём. Не видя скрипта в целом, тут сложно что-то сказать. Но просто 1-2 случая if test ".$a" = ".b", по-моему, ещё не аргумент для полной переписки.
Другой момент тут, что в Unix традиционно роль «шелла с человеческим лицом» была отдана немного другим средствам — Tcl и Perl; Tcl достаточно быстро потерял популярность, но Perl продолжает активно использоваться в таких традициях. Трёхслойный бутерброд типа «C внизу, Perl и шелл» типичен в Unix мире (например, Git, Fidogate), а вот для Python такие случаи сильно реже.
Я подозреваю, имелись в виду Z и N. Вот с ними чётко — по крайней мере после CMP — Z=1, N=0: A==B
Z=0, N=0: A>B
Z=0, N=1: A<B
а вот Z=1, N=1 — не бывает, потому что Z=1 подразумевает старший бит обычного результата (который копируется в N) равным 0.
Для сравнения: S/360 и все её потомки вплоть до ныне живой SystemZ имеют двухбитовое поле condition codes с четырьмя возможными комбинациями, и там после арифметических операций: 0 — результат равен 0, 1 — больше 0, 2 — меньше 0, 3 — произошло знаковое переполнение (и тогда признак знака результата подавлен более важным признаком). Если бы они унесли тогда V отдельно, то оставшиеся N+Z можно было бы совместить в трите.
Но как только выходим на IEEE754 — возникает четвёртый вариант — несравнимость чисел (NaN != NaN) и всё ломает ;\
> и начнете доказывать, что это неверно, потому что shift не сработает, numlock не сработает, через Alt можно ввести несуществующий символ, клавиша на мышке может сместить фокусировку с консоли, а Power вообще может сломать ОС.
Ну, вообще есть байка про то, как сдавали софт каким-то американским воякам, и именно такой подход привёл к появлению надписи «Press any white key». Иначе принимающий генерал жал Shift и Alt и говорил, что программа не работает. (Что они делали с этим, когда начали массово делать клавиатуры без цветового выделения групп клавиш — не знаю. Наверно, предложили просто жать пробел. Но байка осталась.)
И я отношусь к фичам шелла типа автораскрытия параметров с таким подозрением, что лучше перестрахуюсь в триста слоёв проверками типа if test «x$foo» = «xbar»; несмотря на 200% уверенность, что в foo не будет ни '-' ни '=' вначале. Потому что лучше чуть подуть на воду, чем потерять данные или свести с ума систему. А учить всегда лучше хорошему :)
Ну если говорить именно о «железках», то
1. MIPS, Alpha, RISC-V: поля condition codes нет вообще, соответственно, флаг о переполнении не ставится, остаются только младшие 32 или 64 бита (но см. ниже про MIPS). Вообще, стандартные condition codes считаются сейчас наследием CISC, в то время, как новые разработки (с конца 80-х) почти повально RISC, если не что-то более хитрое (IA-64 и NVPTX, например, тоже не имеют condition codes, у них регистры-предикаты — однобитовые булевские значения, ставятся явным сравнением, с ними можно совершать стандартные операции и применять в условных переходах).
LLVM IR тоже спроектирован под RISC-модель (точнее, он ближе даже к подходу с предикатами). Есть встроенные функции вроде sadd_with_overflow, которые явно проверяют переполнение, но обычный код на C/C++ их не зовёт (есть GCC builtins для этого, Clang их поддерживает). Но они не везде хорошо сделаны (для x86 — да, а вот для SystemZ, например, их выхлоп, мягко говоря, ужасен).
2. MIPS, тем не менее, имеет различие, например, между add и addu; второе не проверяет никакого переполнения (даже беззнакового — но оно очень легко проверяется через sltu), а первое — вызывает исключение при знаковом переполнении. Некоторые компиляторы генерируют такое при операциях с signed int, так что при переполнении программа может вылететь без возможности восстановления. А вот возможность проверить факт знакового переполнения без генерации исключения на нём громоздко (или битовые манипуляции, или условная развилка).
Напрямую на обсуждаемое тут с переполнениями это не влияет, но «не как на x86» в полный рост.
Вот чего уже давно не видится нигде — так это иного представления отрицательных целых, чем в дополнительном коде (английский жаргон — twoʼs complement). Вообще не уверен, что после 60-х годов сохранились живые компы с другим подходом. LLVM требует представление в дополнительном коде от платформы реализации; JVM, Go явно его указывают в своих спецификациях. C, C++ по старинке допускают альтернативы, но уже давно непонятно, зачем.
Для текущего времени сделали чисто-юзерлендовое решение через механизм vDSO, а для pidʼа, видимо, не было причин — кому его надо читать таким темпом? Теперь могут и сделать, если что-то резко просядет.
Кстати, в старых FreeBSD вызов getpid() кэшировался на уровне libc; fork() просто снимал флаг «а мы знаем свой pid». vDSO тут даже не обязательно.
Пусть первая часть функции считает, чему равны foo и buka. Вторая — чему равны bar и zuka. Третья использует foo и bar, четвёртая — buka и zuka. Пятая (вы говорили про пять) их как-то обобщает, заодно логгируя.
Чтобы грок (на всякий случай — осознать во всех деталях) всю 150-строчную функцию, читатель должен, в частности, убедиться, что вторая часть не меняет значения foo и buka, а третья — buka и zuka. А то мало ли, что происходит на 4-м уровне вложенности цикла в этих частях и под каким хитрым условием? Защиты ведь от этого нет, нельзя временно навесить const на переменную, а потом его снять. А в случае раздельных функций это выполняется примитивно и прямолинейно — foo и buka просто принимаются как результат 1-й подфункции (неважно, как часть возвращаемого значения, по указателю, по ссылке, как-то ещё) и не передаются во 2-ю, а в 3-ю и 4-ю передаются только по значению (копируются).
Вы можете сказать, что можно их назначить const и они будут таковыми до конца жизни функции. Да, но не в Ruby (в нём нет таких констант), и не в чуть-чуть более сложном случае (пусть эти 5 частей выполняются ещё раз в цикле… не принять изменение из отдельной функции — банально, не разрешить менять — уже серьёзные затраты на изучение каждый раз).
Это был первый аспект. Второй — обеспечение доверия к качеству реализации через тестирование. Про тестирование я уже высказался, повторю чуть иными словами: функция в таком виде слишком неудобна для автоматического тестирования. Нет возможности получить промежуточные результаты, неудобно подавать разные входные данные и контролировать результаты каждого этапа. Для динамически типизированного языка такое тестирование критично; большинство возможных проблем, типа опечаток в именовании полей мап, могло бы быть выловлено в языке типа C++ на стадии компиляции, здесь же этого нет и вся надежда на тесты.
Но там нет возможности запретить видимость переменных внешнего блока. Ни в одном языке программирования, насколько я видел, такого нет; такой запрет делается уже структурированием на функции/процедуры/назови-как-хочешь. И тем более этого нет в Ruby.
Вы можете вспомнить про лямбды в C++, но они фактически отдельные вложенные функции.
Невыгодно. Если требовать от программистов абсолютно высокого внимания ко всем деталям, то придётся выкинуть 99% тех, кто сейчас успешно работает. Повторюсь — в отрасли останутся аутисты и безумные гениальные счётчики в уме. Вы тоже будете выкинуты на мороз, аутисты не пишут такие комментарии на хабр :)
Специфичный алгоритм для каждого сайта? Это ещё сложнее для запоминания.
Точно так же, необходимость следить при визуальной верификации за тем, что результаты предыдущих этапов не портятся до их использования в результате коллизий имён, сильно усложняет визуальную верификацию. (Программную — не рассматриваю, в силу её редкости и дороговизны, но визуальную — вплоть до простого код-ревью — необходимо учитывать и способствовать ей.) Тут ещё и не было необходимости, как мне кажется, экономить на вызовах подфункций.
Как отличники, так и троечники могут обладать высокой внимательностью к деталям, а могут не замечать на ровном месте ошибки типа anspk вместо anpsk (пример реальный с точностью до букв — я с таким боролся в старом фортрановском коде). В крупных неразделённых блоках кода гарантированно следить за целостностью (через длительное развитие и изменение) могут только люди с определёнными классами отклонений (например, аутизм). Таких, увы, сложно найти, большая редкость — чтобы ещё и программировали; но если и найти — их невыгодно использовать из пушки по воробьям.
> Вы просто не знаете, где используется бейсик.
Вы просто домысливаете то, что не писалось. Ну и уводите в бессмысленный оффтопик.
Какой там срок жизни Вселенной сейчас предполагается? И Земля возникла уже при втором поколении звёзд.
А ведь для толкового специалиста невозможно не проходить через этот «нецветной и ненаглядный» этап.
За абонплату допускалось неограниченное количество звонков на город, но по 3 минуты на каждый, дальше принудительно рвалось.
На базе отдыха на Козинке было популярным средством :)
Версия ANTLR ровно по указанному URL.
2. По поводу ассоциативности — Вы не пробовали вместо
stmt: term | term addop stmt;
написать
stmt: term | stmt addop term;
?
по документации — ANTLR спокойно отрабатывает такую левую рекурсию.
Это нужно какие-то спецметоды разрежения применять, чтобы такое получить. В подавляющем большинстве реализаций B, B⁺-деревьев производится оптимизация на заполнение каждой страницы индекса не менее чем на 1/2, 2/3.
Можете указать точный источник страшилки про "10*N"?
Рекомендую определять их наравне с новыми средствами в духе этого C++17.
(Хотя, чтобы корректно выйти из этой ситуации, исходный алгоритм должен делить вход не на две части, а на три, или же пополамить набор элементов, равных опорному. Для quicksort некоторые методы деления отрезка специально заточены на оптимизацию такого варианта. Это можно перенести и на quickselect, но явно.)
Пропущено числительное — сколько именно тысяч
Точно так же, вы или доверяете транслятору и способностям кодера оценить тип выражения, или не доверяете. Если не доверяете, вы не будете использовать автовывод (var в Java, C#, auto в C++...) Если доверяете — вам автовывод не помешает. Вы, как видно, не доверяете. Ну, тоже вариант. Но хотелось бы понять причины такого недоверия.
Я в общем-то тоже в основном не доверяю :), но auto (C++) в итераторах — очевидно удобная возможность, не мешающая правильному использованию. Здесь будет что-то подобное.
А заабьюзить можно что угодно. Самый дурной вариант, мне кажется, это автосужение множества значений — например, при var автовывод дал int, а затем ему пытаются присвоить float и не предполагают возможность усечения до целого. Вот такое должно быть явно прописано в списке подводных камней.
Установленное как 16.04, 16.04.1, будет иметь не-HWE ветку (4.4) до тех пор, пока админ явно не поставит HWE ядра.
Это краткий пересказ убунтовского wiki.
Про апгрейд сложно сказать однозначно, но мои переведённые с 14.04 системы имеют не-HWE ядра.
PS: а можно и явно ставить ядра более поздних лини через соответствующие generic-пакеты.
Другой момент тут, что в Unix традиционно роль «шелла с человеческим лицом» была отдана немного другим средствам — Tcl и Perl; Tcl достаточно быстро потерял популярность, но Perl продолжает активно использоваться в таких традициях. Трёхслойный бутерброд типа «C внизу, Perl и шелл» типичен в Unix мире (например, Git, Fidogate), а вот для Python такие случаи сильно реже.
Z=0, N=0: A>B
Z=0, N=1: A<B
а вот Z=1, N=1 — не бывает, потому что Z=1 подразумевает старший бит обычного результата (который копируется в N) равным 0.
Для сравнения: S/360 и все её потомки вплоть до ныне живой SystemZ имеют двухбитовое поле condition codes с четырьмя возможными комбинациями, и там после арифметических операций: 0 — результат равен 0, 1 — больше 0, 2 — меньше 0, 3 — произошло знаковое переполнение (и тогда признак знака результата подавлен более важным признаком). Если бы они унесли тогда V отдельно, то оставшиеся N+Z можно было бы совместить в трите.
Но как только выходим на IEEE754 — возникает четвёртый вариант — несравнимость чисел (NaN != NaN) и всё ломает ;\
Ну, вообще есть байка про то, как сдавали софт каким-то американским воякам, и именно такой подход привёл к появлению надписи «Press any white key». Иначе принимающий генерал жал Shift и Alt и говорил, что программа не работает. (Что они делали с этим, когда начали массово делать клавиатуры без цветового выделения групп клавиш — не знаю. Наверно, предложили просто жать пробел. Но байка осталась.)
И я отношусь к фичам шелла типа автораскрытия параметров с таким подозрением, что лучше перестрахуюсь в триста слоёв проверками типа if test «x$foo» = «xbar»; несмотря на 200% уверенность, что в foo не будет ни '-' ни '=' вначале. Потому что лучше чуть подуть на воду, чем потерять данные или свести с ума систему. А учить всегда лучше хорошему :)
1. MIPS, Alpha, RISC-V: поля condition codes нет вообще, соответственно, флаг о переполнении не ставится, остаются только младшие 32 или 64 бита (но см. ниже про MIPS). Вообще, стандартные condition codes считаются сейчас наследием CISC, в то время, как новые разработки (с конца 80-х) почти повально RISC, если не что-то более хитрое (IA-64 и NVPTX, например, тоже не имеют condition codes, у них регистры-предикаты — однобитовые булевские значения, ставятся явным сравнением, с ними можно совершать стандартные операции и применять в условных переходах).
LLVM IR тоже спроектирован под RISC-модель (точнее, он ближе даже к подходу с предикатами). Есть встроенные функции вроде sadd_with_overflow, которые явно проверяют переполнение, но обычный код на C/C++ их не зовёт (есть GCC builtins для этого, Clang их поддерживает). Но они не везде хорошо сделаны (для x86 — да, а вот для SystemZ, например, их выхлоп, мягко говоря, ужасен).
2. MIPS, тем не менее, имеет различие, например, между add и addu; второе не проверяет никакого переполнения (даже беззнакового — но оно очень легко проверяется через sltu), а первое — вызывает исключение при знаковом переполнении. Некоторые компиляторы генерируют такое при операциях с signed int, так что при переполнении программа может вылететь без возможности восстановления. А вот возможность проверить факт знакового переполнения без генерации исключения на нём громоздко (или битовые манипуляции, или условная развилка).
Напрямую на обсуждаемое тут с переполнениями это не влияет, но «не как на x86» в полный рост.
Вот чего уже давно не видится нигде — так это иного представления отрицательных целых, чем в дополнительном коде (английский жаргон — twoʼs complement). Вообще не уверен, что после 60-х годов сохранились живые компы с другим подходом. LLVM требует представление в дополнительном коде от платформы реализации; JVM, Go явно его указывают в своих спецификациях. C, C++ по старинке допускают альтернативы, но уже давно непонятно, зачем.
Кстати, в старых FreeBSD вызов getpid() кэшировался на уровне libc; fork() просто снимал флаг «а мы знаем свой pid». vDSO тут даже не обязательно.