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

Антипаттерны

Перевод Go распространенные антипаттерны

13.04.2021 12:15:42 | Автор: admin
Программирование это искусство. Мастера своего дела, создающие потрясающие работы, могут ими гордиться. То же самое относится и к программистам, которые пишут код. Чтобы достичь вершин мастерства, творцы постоянно ищут новые подходы к работе и новые инструменты.

Так поступают и разработчики, которые, не прекращая профессионально развиваться, постоянно стремятся найти ответ на самый важный свой вопрос: Как писать хороший код?. Вот что говорит об этом Фредерик Брукс в книге Мифический человеко-месяц, или Как создаются программные системы:

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


Как писать хороший код (источник)

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

Что такое антипаттерны


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

В качестве простого примера проникновения в код антипаттерна можно привести ситуацию, когда при создании API не учитывается то, как именно потребитель этого API будет им пользоваться. Этому посвящён наш первый рассказ об антипаттерне.

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

Рассмотрим некоторые распространённые антипаттерны, встречающиеся в коде, написанном на Go.

1. Возврат значения неэкспортируемого типа из экспортируемой функции


В Go, для экспорта любого поля или любой переменной, нужно, чтобы имя поля или переменной начиналось бы с большой буквы. Сущности экспортируют из пакетов для того чтобы они были бы видны другим пакетам. Например, если в программе нужно воспользоваться константой Pi из пакета math, то обращаться к ней надо с помощью конструкции math.Pi. Использование конструкции math.pi приведет к возникновению ошибки.

Имена (это относится к полям структур, к функциям, к переменным), которые начинаются с маленькой буквы, являются неэкспортируемыми, они видны только в пакете, в котором они объявлены.

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

// Не рекомендованоtype unexportedType stringfunc ExportedFunc() unexportedType {return unexportedType("some string")}// Рекомендованоtype ExportedType stringfunc ExportedFunc() ExportedType {return ExportedType("some string")}

2. Неоправданное использование пустых идентификаторов


В целом ряде ситуаций присвоение значений пустому идентификатору нецелесообразно. Вот, например, что сказано в спецификации Go об использовании пустого идентификатора в циклах for:

Если последней итерационной переменной является пустой идентификатор, то выражение range эквивалентно такому же выражению без этого идентификатора.

// Не рекомендованоfor _ = range sequence{run()}x, _ := someMap[key]_ = <-ch// Рекомендованоfor range something{run()}x := someMap[key]<-ch

3. Использование циклов или нескольких вызовов append для объединения срезов


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

В следующем примере объединение срезов выполняется путём перебора элементов sliceTwo и присоединения этих элементов к sliceOne по одному:

for _, v := range sliceTwo {sliceOne = append(sliceOne, v)}

Но известно, что append это вариативная функция, а это значит, что её можно вызывать с разным количеством аргументов. В результате предыдущий пример можно значительно упростить и переписать с использованием функции append:

sliceOne = append(sliceOne, sliceTwo)

4. Избыточные аргументы в вызовах make


В Go имеется особая встроенная функция make, которая используется для создания и инициализации объектов типов map (ассоциативный массив), slice (срез), chan (канал). Для инициализации среза с использованием make нужно предоставить этой функции, в виде аргументов, тип среза, его длину и емкость. При инициализации ассоциативного массива с помощью make нужно передать функции размер этого массива.

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

  • В случае с каналами емкость буфера устанавливается в 0 (речь идёт о небуферизованном канале).
  • В случае с ассоциативными массивами размер по умолчанию устанавливается в небольшое начальное значение.
  • В случае со срезами емкость по умолчанию устанавливается в значение, равное указанной длине среза.

Вот неудачный пример использования make:

ch = make(chan int, 0)sl = make([]int, 1, 1)

Этот код можно переписать так:

ch = make(chan int)sl = make([]int, 1)

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

const c = 0ch = make(chan int, c) // Это  не антипаттерн

5. Ненужное выражение return в функциях


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

// Бесполезное выражение return, не рекомендованоfunc alwaysPrintFoofoo() {fmt.Println("foofoo")return}// Рекомендованоfunc alwaysPrintFoo() {fmt.Println("foofoo")}

При этом надо отметить, что возврат с помощью return именованных возвращаемых значений не стоит путать с бесполезным использованием return. Например, в следующем фрагменте кода return возвращает именованное значение:

func printAndReturnFoofoo() (foofoo string) {foofoo := "foofoo"fmt.Println(foofoo)return}

6. Ненужные команды break в выражениях switch


В Go выражения switch устроены так, что при выполнении одного из вариантов кода, описываемого в блоке case, код блоков case, которые следуют за ним, выполняться не будет. В других языках, наподобие C, выполнение кода должно быть явным образом прервано с помощью команды break. В противном случае, если, например, в switch нет ни одного break, после выполнения кода одного блока case выполняется и код следующих за ним блоков. Известно, что эта возможность в выражениях switch используется очень редко и обычно вызывает ошибки. В результате многие современные языки программирования, вроде Go, отказались от такой схемы выполнения выражений switch.

В результате в конце блоков case нет необходимости пользоваться командами break. Это значит, что оба нижеприведенных примера дают один и тот же результат.

// Не рекомендованоswitch s {case 1:fmt.Println("case one")breakcase 2:fmt.Println("case two")}// Рекомендованоswitch s {case 1:fmt.Println("case one")case 2:fmt.Println("case two")}

Но, если нужно, в switch можно реализовать переход к последовательному выполнению кода блоков case. Для этого используется команда fallthrough. Например, следующий код выведет 23:

switch 2 {case 1:fmt.Print("1")fallthroughcase 2:fmt.Print("2")fallthroughcase 3:fmt.Print("3")}

7. Отказ от использования стандартных вспомогательных функций для решения распространённых задач


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

Например, в Go, для организации ожидания завершения выполнения нескольких горутин, можно использовать счетчик sync.WaitGroup. При работе с ним могут применяться вспомогательные функции. В частности функция wg.Add() (переменная wg в наших примерах имеет тип sync.WaitGroup), позволяющая добавить нужное количество горутин в группу. Когда горутина из группы завершает выполнение, счетчик уменьшают, вызывая функцию wg.Add() с передачей ей -1:

wg.Add(1)// ...какой-то кодwg.Add(-1)

Если говорить о конструкции wg.Add(-1), то, вместо того, чтобы использовать её для ручного декрементирования счетчика, можно воспользоваться функцией wg.Done(), которая тоже декрементирует счетчик, уменьшая его значение на 1, но при этом выглядит лучше и понятнее, чем wg.Add(-1):

wg.Add(1)// ... какой-то кодwg.Done()

8. Избыточные проверки на nil при работе со срезами


Длина нулевого (nil) среза приводится к 0. Это значит, что не нужно проверять срез на nil перед проверкой его длины.

Например, в следующем фрагменте кода проверка на nil избыточна:

if x != nil && len(x) != 0 {// выполняем какие-то действия}

Этот код можно переписать, убрав из него проверку на nil:

if len(x) != 0 {// выполняем какие-то действия}

9. Ненужные функциональные литералы


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

fn := func(x int, y int) int { return add(x, y) }

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

fn := add

10. Использование единственного блока case в выражениях select


Выражения select используются при работе с каналами. Обычно они включают в себя несколько блоков case. Но в том случае, если речь идёт об обработке единственной операции, представленной единственным блоком case, использование выражения select оказывается избыточным. В подобной ситуации можно просто воспользоваться операциями отправки данных в канал или их получения из канала:

// Не рекомендованоselect {case x := <-ch:fmt.Println(x)}// Рекомендованоx := <-chfmt.Println(x)

В выражении select может применяться блок default, код которого выполняется в том случае, если системе не удаётся подобрать подходящий блок case. Использование default позволяет создавать неблокирующие выражения select:

select {case x := <-ch:fmt.Println(x)default:fmt.Println("default")}

11. Параметр типа context.Context, который не является первым параметром функции, в которой используется этот параметр


Если функция имеет параметр типа context.Context, то ему обычно дают имя ctx, а при объявлении функции его следует ставить первым в списке параметров. Такой аргумент используется в Go-функциях достаточно часто, а подобные аргументы, с логической точки зрения, лучше размещать в начале или в конце списка аргументов. Почему?

Это помогает разработчикам не забывать об этих аргументах благодаря единообразному подходу к их использованию в различных функциях. Вариативные функции в Go объявляют с использованием конструкции вида elems ...Type, которая должна располагаться в конце списка их параметров. В результате рекомендуется делать параметр типа context.Context первым параметром функции. Подобные соглашения имеются и в других проектах, например, в среде Node.js первым параметром, который передают коллбэкам, принято делать объект ошибки.

// Не рекомендованоfunc badPatternFunc(k favContextKey, ctx context.Context) {// выполняем какие-то действия}// Рекомендованоfunc goodPatternFunc(ctx context.Context, k favContextKey) {// выполняем какие-то действия}

На что стоит обратить внимание тем, кто хочет писать хороший код на Go?

Подробнее..

Перевод Адаптивный дизайн как антипаттерн

27.04.2021 16:10:26 | Автор: admin


Анимированная гифка показывает, что происходит на Invidious, когда я сужаю окно браузера на пару пикселей.

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

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

Пространственный газлайтинг


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

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

Именно такова проблема, которая возникла у меня из-за адаптивного дизайна. Большинство реализаций адаптивного дизайна по сути напоминают редизайн вашего сайта всякий раз, когда пользователь меняет размер окна. Самый вопиющий пример такого рода когда горизонтальная панель меню превращается в вертикальную. В трубу вылетает все то, что пользователь успел зрительно запомнить о расположении ссылок на панели! Думали, эта опция спрятана здесь? Нет же, она там!

Межсайтовая непоследовательность


Но даже если пользователям не нравится адаптивный дизайн, от него особо никуда не денешься. Масса примеров так называемого адаптивного дизайна реализуется при помощи медиа-запросов с применением CSS по сути, это условные операторы, приказывающие браузеру ввести в дело набор правил CSS, если выполняются определенные условия, например, если ширина окна браузера превышает или не превышает заданное количество пикселов. Точное число в данном случае выбирает веб-дизайнер; соответственно, два разных веб-дизайнера почти никогда не выбирают одинаковую ширину.

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

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

Казалось бы, почему пользователь просто не отрегулирует размер окна, если попадет на веб-страницу, для просмотра которой требуется окно пошире. В конце концов, именно так приходилось действовать пользователю до появления адаптивного дизайна, когда была возможность просто переключаться между веб-страницами с заданной фиксированной шириной. Но на самом деле не все так просто: на неадаптивных страницах совершенно ясно, нужно ли изменить размер окна, чтобы в нем поместилась вся страница. А на адаптивной веб-странице пользователю ничто не подсказывает, эй! В такое окно, как у тебя сейчас, вся страница не влезет. Нет ни полосы прокрутки, ни обрезки содержимого; в лучшем (или, полагаю, в худшем) случае есть меню-гамбургер или какой-нибудь другой странный как бы мобильный интерфейс.

Что делать пользователю?


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

Рациональнее было бы каким-то образом заставить браузер придавать единую задаваемую пользователем ширину таблицам стилей и скриптам на всех веб-страницах. Это желательно хотя бы из соображений приватности.

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

Что делать веб-дизайнеру?


Я оставил этот раздел напоследок, поскольку он самый важный. Все вышеизложенное подводит нас к простому выводу:веб-дизайнерам нужно долго и крепко обдумывать, как именно реализовать адаптивный дизайн.

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

  1. Настольный ПК/ноутбук
  2. Планшет
  3. Смартфон

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

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

let mobile = navigator.userAgent.match(/Mobi/);let ipad = navigator.userAgent.match(/iPad/);let android = navigator.userAgent.match(/Android/);if (mobile && !ipad)this is a phoneelse if (ipad || android)this is a tablet

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



Наши серверы можно использовать для разработки и хостинга сайтов.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Категории

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

  • Имя: Макс
    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