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

Fun

Recovery mode Безумный безусловный обмен

08.08.2020 12:09:15 | Автор: admin

Безумный безусловный обмен


image


Недавно попалась мне задача иммутабельным способом поменять местами два элемента в массиве по их индексам. Задача довольно простая. Поэтому решив её разумным способом:


const swap = (arr, ind1, ind2) =>  arr.map((e, i) => {    if (i === ind1) return arr[ind2]    if (i === ind2) return arr[ind1]    return e  })

Захотелось решить её безумным способом. Я подумал, что интересно было бы решить эту задачу:


  • Без операторов сравнения
  • Без циклов и if'ов и тернарных операторов
  • Без использования дополнительных структур данных
  • Без приведения типов

Сведём задачу к меньшей


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


const swap = (arr, ind1, ind2) => {  return arr.map((elem, i) => {    const index = i === ind1 ? ind2 : i === ind2 ? ind1 : i    return arr[index]  })}

Мы создали переменную index, в которой в результате выполнения тернарного оператора будет находится индекс элемента массива с которым нужно обменять текущий элемент. Соответственно если текущий индекс ind1, то его мы заменяем на ind2. Если же текущий элемент находится под индексом ind2 мы его меняем с элементом по индексом ind1. Но если не то, и не другое элемент не должен менятся соответственно и index становится равным индексу текущего элемента.


Вынесем логику вычисления значения переменно index в отдельную функцию getSwapIndex(i, ind1, ind2).


const getSwapIndex(i, ind1, ind2) {  return i === ind1     ? ind2     : i === ind2       ? ind1       : i}const swap = (arr, ind1, ind2) => arr.map((_, i) => arr[getSwapIndex(i, ind1, ind2)])

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


Арифметический аналог булевых значений


Так как нам нельзя использовать приведение типов и условные операции мы не можем использовать булевый тип. Так как у нас на входе функции getSwapIndex находится три числа, а значит оперировать мы можем только числами. Но мы можем использовать числа для представления истинности и ложности, а именно числа 1 и 0. Где 1 будет представлять Истину, а 0 будет представлять Ложь.


Таким образом имеем тип:


type NumberBoolean = 1 | 0

Давайте опишем логическую операцию "ИЛИ" для примера работы с таким типом


const or = (condition1, condition2) => condition1 + condition2 - condition1 * condition2

В самом деле, если на вход этой функции подать числа 1 или 0. То результат будет в точности повторять логику операции "ИЛИ" с обычными булевыми значениями.


or(0, 0) => 0 + 0 - 0 * 0 => 0or(0, 1) => 0 + 1 - 0 * 1 => 1or(1, 0) => 1 + 0 - 1 * 0 => 1or(1, 1) => 1 + 1 - 1 * 1 => 1

Это не единственная возможная реализация этой операции, например такое решение тоже правильное:


const or = (c1, c2) => Math.sign(c1 + c2)

Функция Math.sign

Функция Math.sign возвращает "знак" своего первого параметра:


Math.sign(-23) = -1Math.sign(0) = 0Math.sign(42) = 42

Арифметический аналог тернарного оператора


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


const R = С ? R1 : R2// где С - некоторое булевое условие, R1, R2 - некоторые числа.// тоже,const R = P * R1 + (1 - P) * R2// где Р - это соответсвующее числовое значение булевого значения С. То есть если С === true, то P должно быть равно 1, и если С === false, то P должно быть равно 0.

Если P === 0, то R = 0 * R1 + (1 - 0) * R2 = R2.
Если же P === 1, то R = 1 * R1 + (1 - 1) * R2 = R1.


Таким образом использование тернарного оператора можно заменить на его арифметический аналог.


Давайте определим функцию ternary(c, r1, r2) для подобной операции:


function ternary(p: NumberBoolean, r1: number, r2: number): number {  return p * r1 + (1 - p) * r2}

Сравнение на равенство чисел


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


isEqual(a: number, b: number): NumberBoolean

Давайте посмотрим на её реализацию:


const isEqual = (a, b) => {  return 1 - Math.sign(Math.abs(a - b))}

Функция Math.abs

Функция Math.abs возвращает абсолютное значение числа:


Math.abs(-23) = 23Math.abs(0) = 0Math.abs(42) = 42

Докажем, что это то, что нам нужно. Если a и b равны, то:


isEqual(a, b) => 1 - Math.sign(Math.abs(a - b)) => 1 - Math.sign(Math.abs(0)) => 1 - Math.sign(0) => 1 - 0 => 1

Но если они не равны, то:


isEqual(a, b) => 1 - Math.sign(Math.abs(a - b)) => 1 - Math.sign(Math.abs(Не Ноль)) => 1 - Math.sign(Положительное число)) => 1 - 1 => 0

Таким образом эта функция соответствует сравнению на равенство и возвращает арифметический аналог булевого значения.


К-К-КОМБО


Теперь перепишем нашу функцию getSwapIndex с использованием всех этих функций:


const getSwapIndex = (i, ind1, ind2) =>  ternary(isEqual(i, ind1), ind2, ternary(isEqual(i, ind2), ind1, i))

Таким образом полное решение:


const isEqual = (a, b) => 1 - Math.sign(Math.abs(a - b))const ternary = (p, r1, r2) => p * r1 + (1 - p) * r2const getSwapIndex = (i, ind1, ind2) =>  ternary(isEqual(i, ind1), ind2, ternary(isEqual(i, ind2), ind1, i))const swap = (arr, ind1, ind2) => arr.map((_, i) => arr[getSwapIndex(i, ind1, ind2)])

И в нём нет ни условных операторов, ни сравнений, ни приведений типов.


Фантазия


Конечно это не едиственное решение, я ещё немного поразписывал и получилось тоже не плохое решение:


const getSwapIndex = (i, ind1, ind2) => {  const shouldSwap = or(isEqual(i, ind1), isEqual(i, ind2))  return ternary(shouldSwap, ind1 + ind2 - i, i)}

Послесловие


Спасибо за уделённое время, интересно почитать ваши комментарии и решения!

Подробнее..
Категории: Javascript , Fun

Чистая схемотехника

21.11.2020 12:09:13 | Автор: admin
1 минута на чтение. На написание: 14 часов.

Программисты постоянно ломают копья об оформлении кода, а мы что, хуже?

Цифровые схемы в большинстве своём скучны и неинтересны, так что оформлять будем аналоговые.
Ламповые приборы всегда гудели сочнее кремниевых, поэтому попробуем придать теплоту и ламповость современным схемам.

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

Кодекс это просто свод указаний, а не жёстких законов


Итак, цели:
Развлечься так с пользой;
Вернуть схемам теплоту старых добрых ламповых времён;
Придать удобочитаемость;
Добавить мемности;

Основное правило здравый смысл.

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

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

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

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

Сейчас в тренде всё плоское, поэтому без объёмных, имитирующих вид реальных компонентов символов.

Хотим ламповости, поэтому поменьше углов. Редакторы схем всё же не Photoshop, так что в меру.

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

Результат


LED Driver


Voltage Regulator


Fully Differential Input Buffer


Differential Input Buffer


Заключение


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

Свойства против методов

08.02.2021 12:21:23 | Автор: admin


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


Бэкграунд Java-программистов


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


Возьмём для примера следующий класс:


public class Point {    public double x;    public double y;}

Мы имеем класс, описывающий точку на плоскости. Что с ним не так? Во-первых, так как это открытые поля, то они доступны для редактирования извне. Во-вторых, мы открываем детали реализации, что храним точку в декартовых координатах.


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


public class Point {    private double x;    private double y;    public Point(double x, double y) {        this.x = x;        this.y = y;    }    public double getX() {        return x;    }    public void setX(double x) {        this.x = x;    }    public double getY() {        return y;    }    public void setY(double y) {        this.y = y;    }}

Стандартная практика на Java, в IDE даже есть специальные генераторы для этого.
Мы убрали прямой доступ к полям с помощью методов get и set.
Хотя они выглядят как функции, в сущности, являются геттерами и сеттерами.


Сахар в Котлине


В Котлине у нас есть свойства и мы можем не писать простыню из методов get и set:


class Point(var x: Double, var y: Double)

Выглядит лаконично, не так ли? Обращение к свойству тоже удобно: x вместо getX().


При необходимости мы можем переопределить геттер или сеттер:


var x: Double = 0     set(value) {        if (value >= 0) field = value    }

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


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


Общепринятые соглашения


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


  • если свойство не бросает исключение (exception)
  • дёшево для вычисления (или можно закешировать при первом запуске)
  • возвращает одно и то же значение при каждом вызове, если состояние объекта не изменилось

Я ожидал увидеть более развёрнутое руководство. Для первого пункта я бы добавил про любой сайд-эффект, который может происходить при вызове функции. С последним пунктом тоже не всё так просто.


Например, если у нас есть класс User:


class User(    val firstName: String,    val lastName: String)

Нужно ли полное имя делать свойством или всё же методом?


val fullName get() = "$firstName $lastName"

В данном случае создаётся всегда новый объект String, хотя это деталь реализации. Но значения всегда будут равны при сравнении через equals. На крайний случай можно закешировать fullName, пожертвовав памятью.


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


Сложно или легко вычислимые свойства


Кажется, это довольно расплывчатое требование. Что значит сложно вычислимое? Если подразумеваются тяжёлые вычисления, такие как запрос в сеть или к БД, то мы должны будем вынести вызов в отдельный поток. В этом случае асинхронный вызов будет выглядеть по-другому: метод с коллбэком, реактивный поток или корутина. Но речь, скорее всего, не об этом.


Рассмотрим следующий пример:


class DocumentModel {     val activePageIndex: Int}

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


images.forEach { image ->   document addImage(image, document.activePageIndex)}

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


val pageIndex = document.activePageIndeximages.forEach { image ->   document addImage(image, pageIndex)}

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


Интерфейс важнее реализации


С одной стороны, приведённый выше пример показывает, насколько важно думать об интерфейсе, как он будет использоваться на клиентской стороне. С другой стороны, реализация накладывает ограничение на интерфейс. Если сложные вычисления, то нужно использовать функцию вместо свойства. Здесь мы вступаем в некоторое противоречие, что первичнее, реализация или интерфейс? Мы заранее не можем сказать об эффективности и есть соблазн впасть в крайность всегда делать методы в интерфейсе. Особенно после Java непривычно видеть свойства в интерфейсе. При этом, метод, начинающийся со слова get или set никого не смущает.


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


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


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


Фундаментальные отличия


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


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


  • Состояние. Можно рассматривать как данные, которые описывают характеристики или черты объекта. В этом случае больше подходят свойства.
  • Поведение. То есть то, что можно сделать с объектом. За это отвечают методы.
    Они обычно изменяют состояние.

Это довольно простое правило, которое поможет при выборе свойства или метода.


Методы принято начинать с глагола и если вы не можете подобрать ничего лучше, чем get/set, то это явный признак свойства.

Вместо заключения


Разберём первоначальный пример, только сделаем его интерфейсом:


interface Point {    var x: Double    var y: Double}

Так что с ним не так?


Во-первых, два отдельных сеттера для координат x и y. Когда мы определяем точку в пространстве, то мы задаём их в паре, то есть атомарно. Меняя их независимо, мы создаём возможность для ошибок.


Добавим метод для задания координат и сделаем координаты x и y только для чтения:


interface Point {    val x: Double    val y: Double    fun setCoordinates(x: Double, y: Double)}

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


Расширим интерфейс:


interface Point {    val x: Double    val y: Double    val radius: Double    val angle: Double    fun setCartesian(x: Double, y: Double)    fun setPolar(radius: Double, angle: Double)}

Как мы видим, хороший интерфейс спроектировать не так просто. Хотя можно было бы ограничиться data-классом:


data class Point(    var x: Double,    var y: Double)
Подробнее..

Functional FizzBuzz на Scala

13.06.2020 22:23:27 | Автор: admin

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


Предлагаю вашему вниманию еще один вариант, не совсем пятничный, а скорее субботний: FizzBuzz на Scala, functional style.


Задача


Для чисел от 1 до 100 нужно выводить на экран


  • Fizz, если число делится на 3;
  • Buzz, если число делится на 5;
  • FizzBuzz, если число делится и на 3 и на 5;
  • в противном случае само число.

Решение


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

Начнем с делимости


def divisibleBy(n: Int, d: Int): Boolean = n % d == 0divisibleBy(10, 5) // => true

Нет, это нас не устроит ведь делимость это свойство не только чисел типа Int, опишем делимость в общем виде, а за одно сделаем ее инфиксным оператором (Тут и далее используются некоторые возможности библиотеки cats):


import cats.implicits._import cats.Eqimplicit class DivisionSyntax[T](val value: T) extends AnyVal {  def divisibleBy(n: T)(implicit I: Integral[T], ev: Eq[T]): Boolean = {    import I._    (value % n) === zero  }  def divisibleByInt(n: Int)(implicit I: Integral[T], ev: Eq[T]): Boolean =    divisibleBy(I.fromInt(n))}10 divisibleBy 5 // => trueBigInt(10) divisibleBy BigInt(3) // => falseBigInt(10) divisibleByInt 3 // => false

Тут используются:


  • type class "Integral" требующий от типа "T" возможности вычислять остаток от деления и иметь значение "zero"
  • type class "Eq" требующий от типа "T" возможности сравнивать его элементы (оператор "===" это его синтаксис)
  • расширение типа "T" с помощью extension methods & value classes, которое не имеет рантайм-оверхеда (ждем dotty, который принесет нам нормальный синтаксис экстеншен методов)

Строго говоря метод divisibleByInt не совсем тут нужен, но он пригодится нам позже, если мы захотим использовать литералы целочисленного типа 3 и 5.


FizzBuzz


Отлично! Перейдем к вычислению того, что нужно вывести на экран, напомню, что это может быть "Fizz", "Buzz", "FizzBuzz" либо само число. Тут есть общий паттерн некоторое значение участвует в результате, только если выполняется определенное условие. Для этого подойдет Option, который будет определять используется значение или нет:


def useIf[T](value: T, condition: Boolean) = if (condition) Some(value) else None

Как и в случае с "divisibleBy(10, 5)" и "10 divisibleBy 5" задача решается, но как-то некрасиво. Мы ведь хотим не только решить задачу, но и создать инструмент для ее решения, DSL! По-сути, большая часть работы программиста и есть создание DSL разного рода, когда мы отделяем "как сделать" от "что сделать", "10 % 5 == 0" от "10 divisibleBy 5".


implicit class WhenSyntax[T](val value: T) extends AnyVal {  def when(condition: Boolean): Option[T] = if (condition) Some(value) else None}"Fizz" when (6 divisibleBy 3) // => Some("Fizz")"Buzz" when (6 divisibleBy 5) // => None

Осталось собрать все вместе! Мы могли бы использовать orElse и получили бы 3 правильных ответа из 4, но когда мы должны вывести "FizzBuzz" это не сработает, нам нужно получить Some("Fizz") ? Some("Buzz") => Some("FizzBuzz"). Просто строки можно складывать, но как сложить Option[String]? Тут на помощь нам приходят монады моноиды, cats предоставляет нам все нужные инстансы и даже удобный синтаксис:


  def fizzBuzz[T: Integral: Eq: Show](number: T): String =    ("Fizz" when (number divisibleByInt 3)) |+|    ("Buzz" when (number divisibleByInt 5)) getOrElse    number.show

Тут type class Show дает типу T возможность превращения в строку, |+| синтаксис моноида для сложения и getOrElse задает значение по-умолчанию. Все в общем виде и для любых типов, мы могли бы и от строк "Fizz" & "Buzz" абстрагироваться, но это лишнее на мой взгляд.


Конец


Все, что нам осталось сделать это (1 to 100) map fizzBuzz[Int] и куда-нибудь вывести результат. Но это уже совсем другая история...

Подробнее..

Из песочницы Академия плохого кода переводы строк, пробелы и отступы

18.09.2020 14:13:41 | Автор: admin
Привет, Хабр! Представляю вашему вниманию перевод статьи Dark code-style academy: line breaks, spacing, and indentation автора zhikin2207

image

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

Переводы строк, пробелы и отступы могут убивать.


Как люди читают книги? Сверху вниз, слева направо (по крайней мере большинство). Это же происходит, когда разработчики читают код. Одна строка кода должна содержать одну мысль, следовательно, каждая строка должна содержать только одну команду. Если вы хотите смутить других разработчиков, вам лучше нарушить эти принципы. И давайте я покажу вам как это сделать.

Пример 1

Посмотрите на этот кусок кода. Одна идея на одной строке. Код такой чистый, что меня аж тошнит.

return elements    .Where(element => !element.Disabled)    .OrderBy(element => element.UpdatedAt)    .GroupBy(element => element.Type)    .Select(@group => @group.First());

Мы можем объединить все операторы в одну строку, но это было бы слишком просто. В этом случае мозг разработчика поймёт, что здесь что-то не так, и он разобьёт операторы слева направо. Проще простого!

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

return elements.Where(e => !e.Disabled)    .OrderBy(e => e.UpdatedAt).GroupBy(e => e.Type)    .Select(g => g.First());

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

return elements.Where(e => !e.Disabled)               .OrderBy(e => e.UpdatedAt).GroupBy(e => e.Type)               .Select(g => g.First());

Отправьте мне открытку, если этот подход пройдёт код-ревью в вашей команде.

Совет: оставьте пару операторов на одной строке и пару на отдельных строках.

Пример 2

Абсолютно та же идея здесь. Только такой код вы видите намного чаще.

var result =     (condition1 && condition2) ||     condition3 ||     (condition4 && condition5);

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

var result = (condition1 && condition2) || condition3 ||     (condition4 && condition5);

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

var result = (condition1 && condition2) || condition3 ||              (condition4 && condition5);

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

Совет: играйте с переводами строк для получения лучшего результата.

Пример 3

Что насчёт этого?

if (isValid) {     _unitOfWork.Save();    return true; } else {     return false; } 

Та же проблема, но с другой стороны. Здесь лучшим вариантом будет объединить операторы в одну строку, конечно, расставив фигурные скобки.

if (isValid) { _unitOfWork.Save(); return true; } else { return false; } 

Этот подход будет работать только в случае, если у вас немного операторов в блоках то и иначе. В противном случае ваш код может быть отклонён на этапе код-ревью.

Совет: объединяйте маленькие if/for/foreach операторы в одну строку.

Пример 4

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

bool IsProductValid(    ComplexProduct complexProduct,     bool hasAllRequiredElements,     ValidationSettings validationSettings){    // code}

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

bool IsProductValid(ComplexProduct complexProduct, bool hasAllRequiredElements, ValidationSettings validationSettings){    // code}

Это супер просто: забыть, что было до того, как вы начали скроллить, или пропустить строку, на которой вы начали. Отличный трюк.

Совет: нарочно игнорируйте правило 80 символов.

Пример 5

Пустая строка в правильном месте это сильный инструмент для группировки вашего кода и ускорения его чтения.

ValidateAndThrow(product);product.UpdatedBy = _currentUser;product.UpdatedAt = DateTime.UtcNow;product.DisplayStatus = DisplayStatus.New;_unitOfWork.Products.Add(product);_unitOfWork.Save();return product.Key;

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

ValidateAndThrow(product);product.UpdatedBy = _currentUser;product.UpdatedAt = DateTime.UtcNow;product.DisplayStatus = DisplayStatus.New;_unitOfWork.Products.Add(product);_unitOfWork.Save();return product.Key;

Совет: вставляйте пустые строки рандомно.

Пример 6

Когда вы делаете коммит в репозиторий, у вас есть крошечная возможность посмотреть, что именно вы собираетесь коммитить. НЕ ДЕЛАЙТЕ ЭТОГО! Это нормально, если вы добавили лишнюю пустую строку как здесь.

private Product Get(string key) {    // code}private void Save(Product product) {    // code}

Или, что еще лучше, добавили несколько пробелов на пустой строке (чтобы понять разницу, выделите 5ю строку).

private Product Get(string key) {    // code}    private void Save(Product product) {    // code}

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

Ещё одна дополнительная выгода при использовании лишних пробелов в строке появляется тогда, когда другие разработчики коммитят связанную функциональность, их IDE может автоматически исправлять форматирование. На код-ревью они увидят тысячу красных и зелёных строк. Если вы понимаете о чём я ;)

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

Совет: не смотрите на код перед коммитом.

Пример 7

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

product.Name = model.Name;product.Price = model.Price;product.Count =  model.Count;

Совет: знай своего врага.

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

Развлекайтесь.
Подробнее..

Категории

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

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