Оценка Cognitive Complexity не учитывает (и не должна учитывать) уровень "писателя" / "читателя" кода. Понятно, что "новичок" будет писать код с обилием if/elif/else, try/except и т.д., и в его сугубо личном восприятии так будет понятнее / проще / более явно и т.д. Опытный же специалист (не все, конечно, но многие) скажет, что этот код "сложный" в контексте восприятия и будет прав с точки зрения оценки Cognitive Complexity, т.к. исходя из критериев данной оценки: чем более "линейно" написан код, тем он умозрительно "проще" воспринимается
По поводу "if vs switch": в Python'е с 3.10 версии завезли паттерн-матчинг, причем "продвинутый", c captured patterns и т.д., и все резко бросились его юзать :) Но лично я, поюзав его какое-то время, понял, что для данного языка это около-ненужная "фича", так сами по себе captured patterns не особо то и применимы (конкретно в Python'e), а сам по себе синтаксис "match / case" обязывает создавать +1 уровень "вложенности"
Когнитивная сложность кода, на мой взгляд достаточно важная тема, которой, к сожалению, очень многие (на моем опыте, по крайней мере) напрочь пренебрегают. А все начинается с малого. Вот зачем ты пишешь, например, if/else, если у тебя внутри каждой бранчи return, и else можно просто не писать?)
Лично я для себя выработал железное правило: если есть возможность НЕ создавать лишний скоуп - не создавай его. Если есть возможно обработать исключение иным способом, отличным от try/except - используй именно этот способ.
Дополнительные/излишние скоупы - это зло, которое ведет не только к увеличению когнитивной сложности кода, но и к увеличению вероятности ошибок (причем зачастую довольно глупых, типа UnboundLocalError).
Возможно потому, что инструкции программного кода (в любом ЯП) в принципе описываются «сверху вниз», и любая программа читается человеком аналогично, по тому же принципу цепочку вызовов методов можно разделить на несколько строк (каждый последующий вызов - новая строка), и визуально это будет восприниматься (ИМХО) удобнее, нежели длинная цепочка в одну строку. Лично мне сканить код глазами сверху вниз гораздо комфортнее, чем слева направо. Но согласен, если общая длина инструкции (неважно какой) невелика, то разделение на несколько строк выглядит ужасно)
В общем, как и всегда: it depends
P.S. Касательно приведенных Вами примеров кода: в 1 примере (цепочка вызовов) «столбик», на мой взгляд, лучше/удобнее, во 2-м (создание мэпы) - в одну строку, пожалуй, получше будет
Еще раз: с тайпчекером, или без, — в рантайме код одинаковый.
не совсем понятно, причем тут "тайпчекер", если речь про статическую / динамическую типизацию в целом? В статически-типизированных языках информация о типах переменных (соответственно и об их размере на стэке) известна на этапе компиляции, в динамически-типизированных языках - эта информация вычисляется в рантайме.
Аргумент про "бутылочное горлышко" мне тоже не очень понятен: я могу сколько угодно г*вно-кодить на расте или писать около-идеальный, идеоматически верный код на пайтоне с кучей оптимизаций, в 99.9 % код на расте будет во множество раз быстрее
Вы ведь в курсе, да, что питон — динамически типизирован, а аннотации типов прикручены сбоку и вообще никак в результате на исполняемый код не влияют, да?
в курсе, конечно. Мне даже как-то обидно, что вы подумали, что я могу быть не в курсе :)
Я не знаю, что такое «аффектить», но если имелось в виду «влиять на» — то никак. В рантайме хаскеля (и подавляющего большинства строго типизированных языков) — никаких типов нет из-за type erasure, которую придумали как раз ради повышения производительности.
Я про то, что вычисление типа переменной в рантайме и выделение места на стеке под ее хранение - это runtime-overhead.
По сравнению с чем? С питоном без аннотаций? — Возможно. При этом скала насквозь типизирована, но всё еще порождает анекдоты типа такого:
да, по сравнению с Питоном без аннотаций. Код на Скале (лично для меня) - "темный лес", но связано это с тем, что я не силен в ФП, а не с тем, что Скала - статически типизированный язык
Конечно. Но обычно не с типами
Одна из достаточно частных проблем в Python - это когда функция возвращает, например, Optional[T], а программист использует ее результат, закладываясь на то, что там будет T и только T. Но проблема Пайтона в том, что эта потенциальная проблема в некорректном использовании такой функции может быть идентифицирована только через тайп-чекер, либо же, она проявит себя через N лет и попросту свалит прод :)
Возможно, я изначально не совсем верно понял Ваш посыл, но лично я говорил сугубо в контексте Пайтона, и да, я подсознательно сравнивал Пайтон с тайп-аннотациями и без оных. В целом, если сравнивать различные ЯП, то мой опыт позволяет лишь сравнить пайтон с растом, но не более того
аннотации типов (в том же пайтоне) позволяют сделать код более явным и прозрачным и значительно упрощают жизнь программисту, который большую часть времени читает чужой код
использование аннотаций в связке с тайп-чекерами типа mypy позволяют избежать множества ошибок типизации.
Даже хорошие программисты могут допускать ошибки :) а Ваш тезис что-то сродни «хорошие программисты пишут код без багов»
Я уже молчу про тормознутость буквально всех динамически-типизированных языков (надеюсь, не придется объяснять как именно динамическая типизация негативно аффектит производительность)
P.S. За последние лет так 5 я видел ровно 0 проектов на Пайтоне, где type-annotations были бы опциональными/не использовались вовсе: везде они были обязательными + везде mypy / pyright. Другое дело, что огромное множество Python-программеров не умеют в типы и попросту не хотят с этим разбираться, что вырождается в конечном итоге в г*вно-код c повсеместными Any и т.д. Но тут, опять же, есть решение: хорошо настроенный тайп-чекер бьет за такое по рукам :)
По поводу изменяемости словарей: существует immutable аналог dict - frozendict. К слову, pydantic-модели, как и дэйтаклассы, по умолчанию тоже мутабельные.
Если вам действительно нужна валидация - pydantic-модели это решение. Но, в реальности, валидация не всегда нужна, и тогда pydantic-модели лишь привносят overhead на доп. сериализацию/десериализацию данных (да-да, даже в версии 2 pydantic достаточно тормознутый)
была бы возможность не работать, все бы и не работали вовсе
Не судите по себе :)
необходимость вкусно кушать и покупать себе шмот
В вашем тезисе заложена подмена понятий: «кушать» - это, действительно, необходимость. А вот «вкусно кушать» - прихоть, не более. «Покупать шмот» из той же оперы.
И да, заголовок статьи абсолютно корректный. Зумеры действительно не хотят работать. Ходить на работу и работать на ней - немного разные вещи : )
Гринлеты — это микропотоки на ручном управлении. Микро — значит они в разы меньше потребляют ресурсов по сравнению с обычными (нативными) потоками. Потоки — значит они ведут себя как потоки и обладают всеми их свойствами.
Исходя из данного утверждения у меня несколько вопросов: - могут ли грин-треды быть (сами по себе) распараллелеными на разных ядрах CPU? (предполагаю, что нет) - выполняются ли грин-треды на стэке основного потока (MainThread), или каждый грин-тред имеет свой собственный стэк (как, например, горутины в Go)?
Я, возможно, не знаю каких-то тонкостей, но создание нового `multiprocessing.Process(target=...)` на макОси - это spawn c версии 3.8. Но да, ничего не мешает юзать `multiprocessing.get_context("fork").Process(target=...)`
Про культуру разработки согласен. Но попробуйте объяснить питонисту, что мутабельность - это, в общем-то, плохо: независимо от того, джун перед вами аль синьор, результат будет один - непонимание почему.
Настоящая проблема - возможность в Python мутэйтить все на свете. В данной статье один из примеров того, что делать , по сути, не нужно (причем, не только - "плохо", но и банально - "незачем"), но сам язык это любезно разрешает
Любить язык за то худшее, что в нем есть? Увольте
Я правильно понял, что в 4 кейсе
when
эквивалентенif
в других языках?Можно пример правильно реализованного паттер-матчинга?
Оценка Cognitive Complexity не учитывает (и не должна учитывать) уровень "писателя" / "читателя" кода. Понятно, что "новичок" будет писать код с обилием if/elif/else, try/except и т.д., и в его сугубо личном восприятии так будет понятнее / проще / более явно и т.д. Опытный же специалист (не все, конечно, но многие) скажет, что этот код "сложный" в контексте восприятия и будет прав с точки зрения оценки Cognitive Complexity, т.к. исходя из критериев данной оценки: чем более "линейно" написан код, тем он умозрительно "проще" воспринимается
По поводу "if vs switch": в Python'е с 3.10 версии завезли паттерн-матчинг, причем "продвинутый", c captured patterns и т.д., и все резко бросились его юзать :) Но лично я, поюзав его какое-то время, понял, что для данного языка это около-ненужная "фича", так сами по себе captured patterns не особо то и применимы (конкретно в Python'e), а сам по себе синтаксис "match / case" обязывает создавать +1 уровень "вложенности"
Нет, это не заставляет мозг вставлять пропущенное слово, если программист знает как работает control-flow :)
Когнитивная сложность кода, на мой взгляд достаточно важная тема, которой, к сожалению, очень многие (на моем опыте, по крайней мере) напрочь пренебрегают. А все начинается с малого. Вот зачем ты пишешь, например, if/else, если у тебя внутри каждой бранчи return, и else можно просто не писать?)
Лично я для себя выработал железное правило: если есть возможность НЕ создавать лишний скоуп - не создавай его. Если есть возможно обработать исключение иным способом, отличным от try/except - используй именно этот способ.
Дополнительные/излишние скоупы - это зло, которое ведет не только к увеличению когнитивной сложности кода, но и к увеличению вероятности ошибок (причем зачастую довольно глупых, типа UnboundLocalError).
Возможно потому, что инструкции программного кода (в любом ЯП) в принципе описываются «сверху вниз», и любая программа читается человеком аналогично, по тому же принципу цепочку вызовов методов можно разделить на несколько строк (каждый последующий вызов - новая строка), и визуально это будет восприниматься (ИМХО) удобнее, нежели длинная цепочка в одну строку. Лично мне сканить код глазами сверху вниз гораздо комфортнее, чем слева направо. Но согласен, если общая длина инструкции (неважно какой) невелика, то разделение на несколько строк выглядит ужасно)
В общем, как и всегда: it depends
P.S. Касательно приведенных Вами примеров кода: в 1 примере (цепочка вызовов) «столбик», на мой взгляд, лучше/удобнее, во 2-м (создание мэпы) - в одну строку, пожалуй, получше будет
Раст был приведен сугубо для примера :) Вместо раста я мог бы привести Go/Java/C# - смысл бы от этого не изменился
не совсем понятно, причем тут "тайпчекер", если речь про статическую / динамическую типизацию в целом? В статически-типизированных языках информация о типах переменных (соответственно и об их размере на стэке) известна на этапе компиляции, в динамически-типизированных языках - эта информация вычисляется в рантайме.
Аргумент про "бутылочное горлышко" мне тоже не очень понятен: я могу сколько угодно г*вно-кодить на расте или писать около-идеальный, идеоматически верный код на пайтоне с кучей оптимизаций, в 99.9 % код на расте будет во множество раз быстрее
в курсе, конечно. Мне даже как-то обидно, что вы подумали, что я могу быть не в курсе :)
Я про то, что вычисление типа переменной в рантайме и выделение места на стеке под ее хранение - это runtime-overhead.
да, по сравнению с Питоном без аннотаций. Код на Скале (лично для меня) - "темный лес", но связано это с тем, что я не силен в ФП, а не с тем, что Скала - статически типизированный язык
Одна из достаточно частных проблем в Python - это когда функция возвращает, например,
Optional[T]
, а программист использует ее результат, закладываясь на то, что там будетT
и толькоT
.Но проблема Пайтона в том, что эта потенциальная проблема в некорректном использовании такой функции может быть идентифицирована только через тайп-чекер, либо же, она проявит себя через N лет и попросту свалит прод :)
Возможно, я изначально не совсем верно понял Ваш посыл, но лично я говорил сугубо в контексте Пайтона, и да, я подсознательно сравнивал Пайтон с тайп-аннотациями и без оных.
В целом, если сравнивать различные ЯП, то мой опыт позволяет лишь сравнить пайтон с растом, но не более того
аннотации типов (в том же пайтоне) позволяют сделать код более явным и прозрачным и значительно упрощают жизнь программисту, который большую часть времени читает чужой код
использование аннотаций в связке с тайп-чекерами типа mypy позволяют избежать множества ошибок типизации.
Даже хорошие программисты могут допускать ошибки :) а Ваш тезис что-то сродни «хорошие программисты пишут код без багов»
Я уже молчу про тормознутость буквально всех динамически-типизированных языков (надеюсь, не придется объяснять как именно динамическая типизация негативно аффектит производительность)
P.S. За последние лет так 5 я видел ровно 0 проектов на Пайтоне, где type-annotations были бы опциональными/не использовались вовсе: везде они были обязательными + везде mypy / pyright. Другое дело, что огромное множество Python-программеров не умеют в типы и попросту не хотят с этим разбираться, что вырождается в конечном итоге в г*вно-код c повсеместными Any и т.д. Но тут, опять же, есть решение: хорошо настроенный тайп-чекер бьет за такое по рукам :)
По поводу изменяемости словарей: существует immutable аналог dict - frozendict. К слову, pydantic-модели, как и дэйтаклассы, по умолчанию тоже мутабельные.
Если вам действительно нужна валидация - pydantic-модели это решение. Но, в реальности, валидация не всегда нужна, и тогда pydantic-модели лишь привносят overhead на доп. сериализацию/десериализацию данных (да-да, даже в версии 2 pydantic достаточно тормознутый)
Дикты инварианты, потому что мутабельны. Тип Mapping же ковариантный. К тому же, с аннотацией Mapping я могу подсунуть как dict, так и frozendict.
Мешает чему? Возможности писать не типобезопасный код? Лени разработчика?
Не судите по себе :)
В вашем тезисе заложена подмена понятий: «кушать» - это, действительно, необходимость. А вот «вкусно кушать» - прихоть, не более. «Покупать шмот» из той же оперы.
И да, заголовок статьи абсолютно корректный. Зумеры действительно не хотят работать. Ходить на работу и работать на ней - немного разные вещи : )
«Занятость» и «желание работать» немного разные вещи.
Исходя из данного утверждения у меня несколько вопросов:
- могут ли грин-треды быть (сами по себе) распараллелеными на разных ядрах CPU? (предполагаю, что нет)
- выполняются ли грин-треды на стэке основного потока (MainThread), или каждый грин-тред имеет свой собственный стэк (как, например, горутины в Go)?
Я, возможно, не знаю каких-то тонкостей, но создание нового `multiprocessing.Process(target=...)` на макОси - это spawn c версии 3.8.
Но да, ничего не мешает юзать `multiprocessing.get_context("fork").Process(target=...)`
Про культуру разработки согласен. Но попробуйте объяснить питонисту, что мутабельность - это, в общем-то, плохо: независимо от того, джун перед вами аль синьор, результат будет один - непонимание почему.
Настоящая проблема - возможность в Python мутэйтить все на свете. В данной статье один из примеров того, что делать , по сути, не нужно (причем, не только - "плохо", но и банально - "незачем"), но сам язык это любезно разрешает