Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
ОБ, предлог.
1.
Употр. вместо «о» (2.О):
перед словами, начинающимися с гласной, например: об армии, об искусстве, об отце, об угол;
Практика показала, что огромные методы всегда имеют серьезные проблемы
1) Узнать информацию о поставщике;
2) Узнать информацию о лотах закупки;
3) Узнать информацию о аукционе закупки;
4) Узнать информацию о протоколах закупки;
5) Узнать информацию о последней дате изменения состояния закупки.
Аукцион
- лоты
- закупка
- дата изменения состояния
- протоколы
// разбор заказов
.. 5 шагов..
// разбор лотов
... 8 шагов ...
— мы не увидим в стектрейсе этого названия
— мы не сможем свернуть его в редакторе
— язык не контролирует использование переменных (во-первых можно случайно использовать переменную из верхнего закомментированного блока, во-вторых, при чтении всегда придется учитывать эту структуру
— нет возможности декларировать исходные данные и результаты для каждого куска
Для меня прокрутка менее удобна чем охватывание одним взглядом одним взглядом :)
По-моему, рассматривать организацию процедур, как средство поименования блоков кода некорректно. Изначальный смысл еще с эпохи машинного кода совсем иной — переход к куску и возврат обратно.
Если просто в callstack'е при отладке — не страшно, мы и так видим, где мы сейчас.
Если при exception'е — есть инструменты, которые выдают номер строки (у нас используется Eurekalog для Delphi, в лог при обвале идет callstack с номерами строк в модулях).
В общем, зависит от используемых инструментов.
— мы не сможем свернуть его в редакторе
Ни разу не приходилось сворачивать. Видимо, по-разному с кодом работаем.
— язык не контролирует использование переменных (во-первых можно случайно использовать переменную из верхнего закомментированного блока, во-вторых, при чтении всегда придется учитывать эту структуру
Вполне можно использовать переменные с ограниченной областью видимости (в С++, например) или определять переменные для каждого блока свои и не использовать их повторно.
// разбор файла
{
handle = readFile(name)
.... куча кодв
close (handle)
}
Насчет закомментированного — предпочитаю следовать правилу — никаких закомментированных блоков в репозитории. Комментарии — для комментариев, для остального — система контроля версий.
— нет возможности декларировать исходные данные и результаты для каждого куска
Очень общее утверждение, сложно что-то обсуждать.
Customer parseFile(string fileName)
{
... куча кода
}
// parsing file
... куча кода
Изначальный смысл безразличен, главное — как удобнее.
Не видим, комментарий может быть сверху за границами экрана.
Осталось по номеру определить комментарий, которым был помечен блок и отличить его от комментария к конкретной строке.
Как увидеть общий план?
Я не очень хорошо знаю C++ он позволяет писать так?
2. Ключевые слова — язык не контролирует то есть если вы и будете использовать уникальные переменные для блока легко ошибиться.
трудно понять что код берет в качестве исходных данных, что возвращает,
вообще говоря непонятно, к чему относится комментарий, к первой строке, к 10 строкам или к чему
Не видим, комментарий может быть сверху за границами экрана.
Не далее, чем заголовок метода при той же разбивке. Лично мне удобнее смотреть на код — где я, callstack для того откуда я пришел.
2. Ключевые слова — язык не контролирует то есть если вы и будете использовать уникальные переменные для блока легко ошибиться.
Приведите пример.
// parse file
....
int i = MaxParseIterations;
while (i>0)
{
}
...
// transform
...
while (i < 1000 )
{
}
...
Ну почему же? В вашем примере, если блок предварен чем-то вроде «парсим файл, находим клиента», и при этом есть переменные FileName и Customer, то понять несложно. Если еще использовать что-то вроде венгерской нотации (вроде), то еще проще: например, у нас в команде принято все поля данных в методе предварять F, все локальные переменные — l, формальные параметры — A. тут будет уже lCustomer и AFileName.
Далее — заголовок метода у меня в колстеке (такое окно в дебагерре) а комментарий вообще может быть не в этом окне
например я поставил точку останова в функции MessageBox и вижу колстек main \ ParseFile \ MessageBox а не main \ MessageBox
// parse file
....
int i = MaxParseIterations;
while (i>0)
{
}
...
// transform
...
while (i < 1000 )
{
}
...
То, что вы описали для меня — за гранью.
В-общем, компилятор и тулзы они хорошие — если им рассказать побольше — они помогут, а коментариев они не понимают.
Для меня красная тряпка когда добавляют комментарии в длинный метод разбивая его на части типа:
Используйте IterationIdx и TransformIdx, в чем проблема?
То есть метод разбит на логические куски коментариями
И не надо. Код вы пишете, а не компилятор. С таким же успехом вы можете утверждать, а вдруг, мол, мы используем по ошибке имя не локальной переменной, а поля данных класса.
При наличии такого метода, вам не придётся напрягать лишнюю извилину мозга, когда заказчик произносит: «и тут нужно сложить степень двойки этого полиномиального коэффициента между собой записанное в таком-то файле количество раз».
То что для одного непонятно, для другого — очевидно
Вот вы понимаете, почему ядро атома не разлетается на кусочки в то время как внутри него действуют чудовищные отталкивающие силы? А если вас попросят запрограммировать модель атома, будет ли для вас проблемой то что вы это не понимаете, точно также, как для вас проблема, что вы не понимаете почему в DES-е 16 итераций?
Самый простой способ эти абстракции придумать — разбить алгоритм на «Функция имени меня», «Функция имени вон той планеты», «Функция имени моего домашнего животного»,
let DES x = x |> splitToBlocks |> encryptBlock |> joinBlocks
let encryptBlock x = x |> initialPermutation |> encryptLoops mainEncryption |> finalPermutation
И если раздробите вы плохо декомпозирующийся алгоритм на части, понятнее он от этого не станет.
Примеры я уже приводил.
Схема шифрования алгоритма DES
4.1 Начальная перестановка
4.2 Циклы шифрования
4.3 Основная функция шифрования (функция Фейстеля)
4.4 Генерирование ключей
4.5 Конечная перестановка
Если же вам придётся переводить сложную абстракцию постановщика user-story в непонятно для чего изобретённую вашу сложную абстракцию, то ваш мозг быстро закипит. В результате вы напишите гораздо больше кода с гораздо большим количеством ошибок. Советую вам почитать книгу о DDD Эрика Эванса.
«вы зачем-то переносите из википедии код не 1-в-1 а инлайня функции»
Это было сделано намеренно.
Первая цель — показать, как авторы самого алгоритма занимались декомпозицией. Вторая цель — так как мы модифицируем алгоритм, содержимое «функции Фейстеля» изменяется, и это больше не «функция Фейстеля» (т.к. она делает другие операции).
Соответственно, мы не можем использовать ту же самую декомпозицию и туже самую абстракцию, что использовали авторы DES, и толк от неё теряется.
«Циклы шифрования»
Что выражает название метода «Циклы шифрования»? Что это что-то, как то связанное с шифрованием и что внутри есть цикл. То что это связано с шифрованием, итак ясно из контекста использования метода. То что в названии функции отражено что внутри есть цикл — это вообще нарушение инкапсуляции. Это как раз пример плохой синтетической абстракции, о которой я говорил ранее.
let encryptionStep encryptBlockHalf (Left,Right) key = Right, (Left xor (encryptBlockHalf key))
let encryptionLoops block encryptBlockHalf keys = keys |> fold (encryptionStep encryptBlockHalf) (splitBlock block)
Это всё равно что дать методу имя «Сделать хорошо», а классу — «Manager» или «Service». Эти названия ничего не отражают.
# 1)Разбить исходный текст на блоки по 64 бита
def modifiedDES(stream, key):
#2)Для каждого такого блока
blocks = split_to_blocks(stream)
for block in blocks:
encryptBlock(block, key)
# {
def encryptBlock(block):
# 2.1)Переставить биты в блоке местами по определённому алгоритму
initialPermutation(block)
# 2.2)Разбить блок на два блока длиной 32 бита (далее — левый и правый)
(left, right) = splitBlockToHalves(block)
# 2.3)Повторить 16 раз:
subKeys = generateSubkeysFrom(key)
for subKey in subKeys:
# {
# 2.3.1)Вычислить Ki из ключа шифрования K и номера итерации по определённому алгоритму.
# 2.3.2)Высичлить блок E длиной 48 из правого блока длиной 32 по определенному алгоритму.
expansion = expand(right)
# 2.3.3)F = побитовая сумма Ki и E
F = subKey xor E
# 2.3.4)левый блок длиной 32 = правый блок длиной 32 бита на прошлой итерации
# 2.3.5)правый блок длиной 32 = левый блок 32 на прошл итерации, побитово сложенный с F
(left, right) = (right, left xor F)
# 2.3.6)Добавить левый блок в конец результата шифрования
result.write(left)
# 2.3.7)Добавить правый блок в конец результата шифрования
result.write(right)
# итерация 1
def modifiedDES(stream, key):
blocks = split_to_blocks(stream)
for block in blocks:
encryptBlock(block, key)
def encryptBlock(block):
initialPermutation(block)
(left, right) = splitBlockToHalves(block)
subKeys = generateSubkeysFrom(key)
for subKey in subKeys:
expansion = expand(right)
F = subKey xor E
(left, right) = (right, left xor F)
result.write(left)
result.write(right)
# итерация 2
def modifiedDES(stream, key):
blocks = split_to_blocks(stream)
for block in blocks:
encryptBlock(block, key)
def encryptBlock(block):
initialPermutation(block)
(left, right) = splitBlockToHalves(block)
subKeys = generateSubkeysFrom(key)
for subKey in subKeys:
(left, right) = (right, left xor encryptBlockHalf(subkey, right))
result.write(left)
result.write(right)
def encryptBlockHalf(subKey, blockHalf):
# основная функция шифрования
return subKey xor expand(blockHalf)
# итерация 3
def modifiedDES(stream, key):
blocks = splitToBlocks(stream)
for block in blocks:
encryptBlock(block, key)
def encryptBlock(block):
initialPermutation(block)
subKeys = generateSubkeysFrom(key)
encryptBlockByHalves(block, subkeys, encryptBlockHalf)
def encryptBlockByHalves(block, subkeys, encryptBlockHalf):
(left, right) = splitBlockToHalves(block)
for subKey in subKeys:
(left, right) = (right, left xor encryptBlockHalf(subkey, right))
result.write(left)
result.write(right)
def encryptBlockHalf(subKey, blockHalf):
# основная функция шифрования
return subKey xor expand(blockHalf)
Абстракция «Зашифровать полублок» — неудачная, т.к. полублок вы дешифровать не сможете (без второго полублока)
е. там внутри 1)узнать информацию о лотах закупки 2)о протоколах 3)о отзывах 4)о прогнозах 5)о поставщике, и ещё много чего
В очередной раз повторюсь, что там мы немного модифицируем «Функцию Фейстеля», после чего она «Функцией Фейстеля» не является,
Допустим, «Кусок1» изменяет состояние класса так, что это нарушает контракт «Куска2». Или результат вычислений «Куска1» передаётся в «Кусок2», нарушая его контракт. Как понять по названию «Куска2», какой у него контракт?
Конструкции вида Contract.Assert() инлайнятся вместе с кодом.
Юнит-тесты же хороши для описания user-story
и для контроля соблюдения контракта, но я никогда не видел хороших примеров описания контракта тестами.
// Блочный шифр - это шифр который шифрует локи отдельно
let encryptByBlocks encryptBlock source =
source |> splitToBlocks |> map encryptBlock |> joinBlocks
// des - это блочный шифт
let des key = encryptByBlocks (encryptBlockUsingDes key)
// шифрование блока это первичная перестановка, потом шифрование
// фейстеля с использованием функции фейстеля в качестве round функции
// потом конченая перестановка
let encryptBlockUsingDes block key =
block |> initialPermutation |> encryptByFeistel feistelFunction key |> finalPermutation
// Сеть фейстеля
let encryptByFeistel roundFunction key block =
let subKeys = splitKey key
subkeys |> fold
(fun (left, right) subkey -> (right, left xor (rountFunction right)))
(splitBlock block)
// модифицированный DES это тоже блочный шифр
let modifiedDes key = encryptByBlocks (encryptBlockByModifiedDes key)
// Вместо функции фейстеля в качестве раунд фунции используем xor
let encryptBlockUsingModifiedDes block key =
block |> initialPermutation |> encryptByFeistel (^^^) key |> finalPermutation
А это приводит к ещё одной интересной мысли — не стоит заниматься декомпозицией и выносить действия в отдельную метод не только когда эти действия не имеют общей цели (или имеют, но нам об этом неизвестно), но и тогда, когда действия имеют общую цель, но для осознания этого программисту потребуется больше усилий, нежели сэкономит декомпозиция.
Точно так же глубокое изучение криптографии, вплоть до понимания что общего у round функций всех шифров (а если нет ничего общего, то это никакая не абстракция), только ради того, чтобы декомпозиция модифицированного DESa приносила пользу, а не вред,
Для примера с сайтом нужно немного воображения,
примеры с танцем и модифицированным DES-ом могут стать реальными при некотором стечении обстоятельств.
class Закупка
{
Дата датаИзменения;
Аукцион аукцион;
List<Протокол> протоколы;
}
class SiteParseResult{
Запкупка закупка;
Лоты лоты;
}
SiteParseResult parseSite()
{
return new SiteParseResult(){
закупка = parseЗакупка();
лоты = parseЛоты();
)
}
1) Узнать информацию о поставщике;
2) Узнать информацию о лотах закупки;
3) Узнать информацию о аукционе закупки;
4) Узнать информацию о протоколах закупки;
5) Узнать информацию о последней дате изменения состояния закупки.
Допустим, есть модуль, скачивающий страницы с некоторого сайта и распарсивающий их.
1) Узнать информацию о поставщике;
2) Узнать информацию о лотах закупки;
3) Узнать информацию о аукционе закупки;
4) Узнать информацию о протоколах закупки;
5) Узнать информацию о последней дате изменения состояния закупки.
class CalculateSomeShit
ACTIONS = %i(
SomeShit1
SomeShit2
SomeShit3
SomeShit4
SomeShit5
)
class SomeShit1
def initialize(context)
@context = context
end
def call
@context[:shit1] = calculate(fetch)
end
private
def fetch
end
def calculate
end
end
class SomeShit2
def initialize(context)
@context = context
end
def call
@context[:shit2] = calculate(fetch(@context[:shit1]))
end
private
def fetch
end
def calculate
end
end
def call
{}.tap do |context|
ACTIONS.each do |action|
context = action.new(context).call
end
end
end
end
Существует миф, что, если в функции больше чем n или меньше чем m строк кода, то с функцией есть проблемы в плане проектирования…
Если при изменении какого-то типа функционала… Если же при изменении какого-то типа функционала ...
А когда функция находится в боевом применении и работает, то причины на её изменение для следования каким-то стандартам должны быть очень вескими
Для криптоалгоритма… необходимо… произвести декомпозицию в соответствии с этим описанием
Для танца логично применить паттерн интерпретатор.
Проблема как раз в том, что декомпозиция алгоритма в коде должна соответствовать декомпозиции алгоритма на едином языке доменной модели (Ubiquitous Language), а над единым языком программист не имеет власти.
Думаю, дело ещё и в том, что вы не понимаете, что такое паттерн проектирования.
Присесть и покудахтать — это две совершенно разные операции, а не вариации одной и той же. Синтаксических деревьев тоже нет, алгоритм танца линеен.
Где в примере с закупкой сложная подсистема, с которой настолько сложно взаимодействовать, что нужно выносить взаимодействие с ней в отдельный объект
Миф о идеальном количестве строк в методе