LINUX.ORG.RU

Повторное использование кода

 , ,


1

2

Как определяется, в какую сторону должно быть обобщение?

То есть, при решении конкретной задачи как определить, что должно быть параметром, а что — неизменяемой частью.

Например, есть задача «получить сумму от 1 до 100».

Самым быстрым решением будет

class Sum1_100
{
  int run() { return 5050; }
}

но в этом случае программист поработал за компьютер. Тривиальное решение с расчётом компьютером

class Sum1_100
{
  int run() 
  { 
    int res = 0; 
    for(int i=1; i<=100; i++) res+=i; 
    return res; 
  }
}

Но получив такую задачу, почти всегда решение выглядит примерно так:

class Sum1_n
{
  int n;
  Sum1_n(int _n = 100) { n = _n; };
  int run() 
  { 
    int res = 0; 
    for(int i=1; i<=n; i++) res+=i; 
    return res; 
  }
}

на случай, если потребуется не до 100, а до какого-то другого числа.

И вот здесь у меня вопрос: почему в «получить сумму от 1 до 100» большинство параметризуют именно 100? Посему не «сумму» (тогда параметром будет операция от 100 элементов) или не «от 1 до 100» (тогда параметром будет некая последовательность). Или вообще не всё сразу? С вызовом типа

  auto_ptr<Op> op = new GenOp(operator+, 100);
  auto_ptr<Seq> seq = new Seq(1, 100);
  auto_ptr<Apply> = new GenApply(op, seq);
  result = Main->run();

Как для произвольного алгоритма определяется, что является параметром, а что неизменяемой частью?

★★★★★

Последнее исправление: monk (всего исправлений: 2)

Ответ на: комментарий от monk

класс со статическим методом

Говнокод.

Трактуй их как класс со статическим методом

Ну хорошо. Тогда как минимум есть 2 разных задачи: получение «дополнения» строки до нужного числа символов и склейка этого «дополнения» с исходной строкой (слева, либо справа). И внезапно это почти тоже самое, что и в твоём примере с суммой. Вот это я понимаю, повторное использование кода.

no-such-file ★★★★★
()
Ответ на: комментарий от Virtuos86

Посмотрел на реализацию sum:

fn sum<S>(self) -> S where
    S: Sum<Self::Item>, 

Sums the elements of an iterator.

Takes each element, adds them together, and returns the result.

Лучше, наверное, тогда так:

fn sum(start: usize, end: usize, delta: usize) -> usize {
    let n = 1 + (usize::max(start, end) - usize::min(start, end)) / delta;
    ((start + end) * n) / 2
}

fn main() {
    use std::ops::Range;
    let seq = Range { start: 1, end: 100 };
    let res = sum(seq.start, seq.end, 1);
    println!("{}", res);
}

Virtuos86 ★★★★★
()
Ответ на: комментарий от no-such-file

Говнокод.

Как это формально определить (какие признаки в наличии)? Как эта же задача должна быть решена не говнокодом?

Тогда как минимум есть 2 разных задачи: получение «дополнения» строки до нужного числа символов и склейка этого «дополнения» с исходной строкой (слева, либо справа).

https://github.com/jonschlinkert/pad-left не говнокод?

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Как это формально определить (какие признаки в наличии)?

Для изменения поведения кода, где вызывается статический метод, придётся копипастить. Копипаста - это говнокод по определению.

Как эта же задача должна быть решена не говнокодом?

Какая «эта»?

не говнокод?

Он самый. repeat прибит гвоздями, а должен быть параметром (функцией в объекте). Нормализация ch и str должны быть отдельными функциями. Сложение repeat + str должно быть отдельной функцией. Каждая функция должна решать одну задачу, а leftPad решает несколько задач.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)

Тред не читал, но пример от ТС-а напомнил байку одного университетского преподавателя, с которым мы как-то за рюмкой чая обсуждали обучение студентов программированию и то, какие клинические случаи при этом попадаются. Так вот дал этот преподаватель студентке математического факультета задание: написать программу, в которой создается матрица 100x100, пользователь с клавиатуры вводит значения для этой матрицы построчно, затем (тут я деталей уже не помню, ну да не суть) распечатываются элементы главной диагонали.

Время действия — самое начало 90-х, когда персональные компьютеры в отдельных ВУЗ-ах только-только начинали появляться. Поэтому для студентов-математиков программирование было непрофильным предметом, но поскольку это веяние времени, то хоть как-то, да изучать программирование приходилось.

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

Ответ был шедевральным: «Я не успеваю вводить данные». А сама при этом чуть не плачет... Мол, каждый раз стараюсь, стараюсь, у меня уже все данные подготовлены, я уже научилась их вводить практически не глядя, а времени на ввода данных все равно не хватает.

Выяснилось, что студентка довольно быстро написала эту программу, но каждое последующее занятие в дисплейном классе она тратила на то, чтобы ввести 10000 элементов матрицы 100x100. Соответственно, она не могла получить и распечатать результаты работы своей программы.

Когда преподаватель пришел в себя и обрел дар речи, он спросил, а почему же она для отладки не могла сделать матрицу поменьше, например, 5x5, на что студентка не менее шедеврально ответила: «Но ведь в условии было сказано, что матрица 100x100, а с матрицей 5x5 — это уже другая задача».

ИМХО, пример ТС-а с суммой чисел от 1 до 100 ничем не отличается от этой истории с матрицей 100x100.

eao197 ★★★★★
()
Ответ на: комментарий от no-such-file

repeat прибит гвоздями, а должен быть параметром (функцией в объекте). Нормализация ch и str должны быть отдельными функциями. Сложение repeat + str должно быть отдельной функцией.

Обалдеть... А существует тогда пример не-говнокода?

Каждая функция должна решать одну задачу, а leftPad решает несколько задач.

Одну: дополнить строку слева заданным символом. Какую ещё задачу он решает?

monk ★★★★★
() автор топика
Ответ на: комментарий от eao197

на что студентка не менее шедеврально ответила: «Но ведь в условии было сказано, что матрица 100x100, а с матрицей 5x5 — это уже другая задача»

вообще-то, действительно другая задача, т.к. в 64Кб помещается только матрица 90x90 восьмибайтных чисел :)

А преподу — незачёт. Про ввод данных из файла, например, в Фортране рассказывают на первом занятии.

anonymous
()

почему в «получить сумму от 1 до 100» большинство параметризуют именно 100? Посему не «сумму»

Если у тебя куча времени можно таких абстракций нагородить, вплоть до докторской диссертации и в итоге в рамках задачи «получить сумму от 1 до 100» можно дойти до в чём смысл жизни и что такое бесконечность.

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

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

foror ★★★★★
()

Пока у тебя один вызов делай SUM_1_TO_100 = 5050

Когда у тебя несколько вызовов то делай SIM_1_TO_100 = sum1to(100), SIM_1_TO_200=sum1to(200) ...

Когда у тебя будет динамическая верхняя граница будешь пользовать тот же sum1to(...)

Когда понадобится двигать нижнюю границу сделаешь

sunAtoB(a, b){
   ...
}
sum1to(b){
   return sumAtoB(1, b);
}
ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 2)
auto_ptr<Op> op = new GenOp(operator+, 100);
auto_ptr<Seq> seq = new Seq(1, 100);
auto_ptr<Apply> = new GenApply(op, seq);
result = Main->run();

да ну нахер, севупле

template <typename G, typename D>
D f(const G &g, const D &d) {return g(d);}

anonymous
()
Ответ на: комментарий от anonymous
for {set sum 0; set counter 0} {$counter < 100} {incr counter ;incr sum $counter} {}
puts $sum 

короче:

proc iseq {first last {step 1}} {
  for {set ilist {} ;set x $first} {$x<=$last} {set ilist [lappend ilist $x] ;incr x $step} {}
  return $ilist
}

expr [join [iseq 1 100] +]
anonymous
()
Ответ на: комментарий от anonymous
for {set sum 0; set counter 0} {$counter < 100} {incr counter ;incr sum $counter} {}
puts $sum 

короче:

proc iseq {first last {step 1}} {
  for {set ilist {} ;set x $first} {$x<=$last} {set ilist [lappend ilist $x] ;incr x $step} {}
  return $ilist
}

expr [join [iseq 1 100] +]
anonymous
()
Ответ на: комментарий от anonymous
: isumconst
  create 1+ 0 swap 1 do i + loop ,
  does> @
;

100 isumconst sum100
  5 isumconst sum5

sum100 .
sum5 .

anonymous
()
class Sum1_n
{
  int n;
  Sum1_n(int _n = 100) { n = _n; };
  int run() 
  { 
    int res; 
    for(int i=1; i<=n; i++) res+=i; 
    return res; 
  }
}

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

sum(a,b): return (a+b)*(b-a+1)/2
aquadon ★★★★★
()
Последнее исправление: aquadon (всего исправлений: 2)
Ответ на: комментарий от aquadon

"Преждевременная оптимизация - корень всех зол в программировании"

Оригинал:
Premature optimization is the root of all evil

Источник:
1974 год, лекция Дональда Кнута, посвященная вручению премии Тьюринга, Computer Programming as an Art, Communications of the ACM, Volume 17, Issue 12, Dec. 1974 (see p.671)).

Полная версия оригинала:
«The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.»

anonymous
()

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

P.S. Не удивлюсь, если это не оптимальный вариант, просто в моей голове это наиболее простой вариант.

aquadon ★★★★★
()
Последнее исправление: aquadon (всего исправлений: 1)
Ответ на: комментарий от monk

А существует тогда пример не-говнокода?

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

Одну: дополнить строку слева заданным символом

А если нужно дополнить строку слева заданными двумя символами - это другая задача? Как у студентки из рассказа выше?

no-such-file ★★★★★
()
Ответ на: комментарий от monk

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

В твоем примере критерий сложно найти, т.к. он простой. Из практики, стоит выделять то, что сходу не напишешь и даже не скопипастишь, например алгоритм над сложными данными, графами какими-то и пр. Причем тут опять же важно не то, выделишь ты его в отдельный интерфейс или нет, а то, прибит ли он гвоздями к текущему кейсу. Если не думая написать реализацию какого-нибудь jpeg, то там внутри столько матана будет на стены наклеено, что при необходимости переиспользования придется вырубать его из стен, вместо того чтобы отвязать в ключевых местах и параметризовать. А так у тебя есть libjpeg, который параметризуется 10 переменными и собственными классами, и еще раз его отлаживать не надо. Даже если он будет реализован как внутренний полу-апи, это все равно декомпозиция с возможностью вынуть и реюзать.

Можно еще что-то взять критерием-без-кейса, т.к. в поддержке и разработке полно нюансов и без конкретных требований. Например чтобы функции умещались в 100 строк, а переменных на функцию было не более 10-15.

anonymous
()
Ответ на: комментарий от aquadon

aquadon

sum(a,b): return (a+b)*(b-a+1)/2

Как это обобщить на применение произвольной бинарной операции к обобщённой последовательности?

ugoday ★★★★★
()
Ответ на: комментарий от monk

А потом приходит другой программист и начинает ругаться на «быдлокрд».

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

Параметризировать 100 и операцию... Но тогда нужно параметризировать 1. И шаг. Можно даже направление, по убыванию или возрастанию, а то -1 не интуитивно, скажут костыль. Ну и еще булевый параметр, допустим у нас от 1 до 100, шаг 3, включать 102 или нет. МОЖЕТ ПРИГОДИТЬСЯ В БУДУЩЕМ. Саму операцию наращивания шага параметризировать не будем, наверное. Ой, у нас уже 6 параметров, засунуть их в одну функцию будет говнокод. Надо разбить на два класса, в один пойдет диапазон, в другой шаг. Можно даже операцию в третий, чтобы было по канонам ООП. Ну и чтобы плодить эти классы надо бы фабрику. Гм, что-то сильно много намучено, прикрутим еще фасад. И пусть эта с-ка, другой программист, посмеет сказать, что тут быдлокод!

goingUp ★★★★★
()
Последнее исправление: goingUp (всего исправлений: 1)
Ответ на: комментарий от aquadon

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

Тащемта формулу расчета суммы арифметической прогрессии я уже выше притащил в тред: Повторное использование кода (комментарий)

Virtuos86 ★★★★★
()
Ответ на: комментарий от ugoday

Как это обобщить на применение произвольной бинарной операции к обобщённой последовательности?

Задача из ОП звучит как: «получить сумму от 1 до 100». Нет ни «произвольной бинарной операции», ни «обобщённой последовательности» (от 1 до 100 — это диапазон).

Virtuos86 ★★★★★
()
Ответ на: комментарий от no-such-file

Относительно любого кода можно только сказать, что в нём плохо.

Перфекционизм — это ужасно. К счастью, лечится.

А если нужно дополнить строку слева заданными двумя символами - это другая задача? Как у студентки из рассказа выше?

Ну, с учётом того, что алгоритм становится слегка другим, то явно другая задача. Так как для двух символов приходится делать дополнительную операцию, если разницы между длиной строки и запрашиваемой длиной является нечётной. И необходимо решить (или вынести в параметр) будут символы «аб» для разницы в 5 формировать «абаба» или «бабаб».

Но ты утверждал, что leftPad решает несколько задач. Какие?

monk ★★★★★
() автор топика
Ответ на: комментарий от goingUp

Надо разбить на два класса, в один пойдет диапазон, в другой шаг. Можно даже операцию в третий, чтобы было по канонам ООП. Ну и чтобы плодить эти классы надо бы фабрику. Гм, что-то сильно много намучено, прикрутим еще фасад. И пусть эта с-ка, другой программист, посмеет сказать, что тут быдлокод!

Благодарю! Именно после наблюдения очередного монстра в таком стиле (https://bitstorm.org/gameoflife/code/gameoflife-uml.png) и родилось желание получить объективный критерий «хорошего кода».

monk ★★★★★
() автор топика
Ответ на: комментарий от ugoday

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

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

Как это обобщить на применение произвольной бинарной операции к обобщённой последовательности?

А почему именно на эти категории? Почему не на применение произвольной операции с произвольным количеством аргументов обходя произвольную структуру данных?

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

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

Боюсь ваша задача не имеет решения. По крайней мере пока человеческое мышление не унифицированно. В зависимости от предыдущего опыта разным людям могут показаться более элегантными разные подходы от «применение произвольной операции с произвольным количеством аргументов обходя произвольную структуру данных» до «echo 5050» со всеми остановками. И все они будут правы.

ugoday ★★★★★
()
Ответ на: комментарий от monk

алгоритм становится слегка другим

Алгоритм чего становится другим? Вообще-то код функции leftPad при этом никак не меняется.

leftPad решает несколько задач. Какие?

Я же перечислил Повторное использование кода (комментарий). Для того чтобы выполнить свою собственную задачу leftPad решает дополнительные задачи, например, нормализацию параметров. Она не должна этим заниматься.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от aquadon

Постоянно слышку «матиматика програмизду нинужна».

Не от всех обобщиний есть польза.

Ну, ладно. Хоть узнали как быстро вычислить 5050. И то польза.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Как это обобщить на применение произвольной бинарной операции к обобщённой последовательности?

Очень легко обобщить. На самом деле пример в ОП уже обобщает. А именно использовать

worker.apply(op, seq)
seq может быть структурой данных, хранящей параметры последовательности (начало, конец, шаг и т.п.). apply применяя operator+ к такой специальной структуре может использовать формулу. А если seq это просто массив, то использовать цикл. Как видишь никаких проблем нет.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

worker.apply(op, seq)

И это поможет чудесным образом провести вычисление за константное время? Нет? Грустно.

ugoday ★★★★★
()
Ответ на: комментарий от monk

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

e1nste1n ★★★★★
()
Ответ на: комментарий от ugoday

В зависимости от предыдущего опыта разным людям могут показаться более элегантными

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

monk ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

Алгоритм чего становится другим? Вообще-то код функции leftPad при этом никак не меняется.

Если в leftPad(str, num, ch) вместо одного символа в ch передать два символа, то результирующая строка не будет иметь длину num. Чтобы leftPad правильно обрабатывал произвольное число символов в ch надо менять алгоритм

monk ★★★★★
() автор топика
Ответ на: комментарий от ugoday

И это поможет чудесным образом провести вычисление за константное время?

В начало worker можно запихнуть проверку:

...
if(isArithSeq(seq)) {
  if(op == (+)) {
     return (seq.begin + seq.end)*(seq.end-seq.begin+1)/2;
  }
}
...

monk ★★★★★
() автор топика

Степанов (который STL придумал) недавно написал книгу «От математики к обобщённому программированию». Релевантность ОП — около 100%.

anonymous
()
Ответ на: комментарий от monk

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

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

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

ugoday ★★★★★
()
Ответ на: комментарий от monk

В начало worker можно запихнуть проверку:

А смысл делать обобщённое решение, если в нём нужно хардкодить обработку конкретных случаев?

ugoday ★★★★★
()
Ответ на: комментарий от monk

Чтобы leftPad правильно обрабатывал произвольное число символов в ch надо менять алгоритм

Да. Какой? Не вижу ответ на этот вопрос. Вижу попытку крутить жопой.

no-such-file ★★★★★
()
Ответ на: комментарий от monk

запихнуть проверку

Не нужно ничего запихивать. Нужно просто сделать мультиметод.

no-such-file ★★★★★
()
Ответ на: комментарий от ugoday

Нет? Грустно.

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

no-such-file ★★★★★
()
Ответ на: комментарий от ugoday

Добавлю, исходная задача звучит почти как «какой язык самый простой и понятный?». Самый правильный ответ: в России --- русский, в Германии --- немецкий, в Китае --- китайский. Практический вывод: попытка разговаривать с финнами на суахили (а суахили много проще финского) скорее всего провалится.

ugoday ★★★★★
()
Ответ на: комментарий от no-such-file

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

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

забыли посмотреть, а какой вопрос, собственно, задавался

Напротив, я прочитал всю тему, включая твой тупняк о «хардкодить обработку конкретных случаев». У тебя сложности с удержанием в голове общего контекста обсуждения?

no-such-file ★★★★★
()
Ответ на: комментарий от ugoday

Добавлю, исходная задача звучит почти как «какой язык самый простой и понятный?»

Для языков есть ответ: «эсперанто».

Хотя вопрос неполон. Если «какой язык самый простой и понятный при изучении?», то эсперанто, а если «какой язык в среднем самый простой и понятный для случайно встретившегося жителя Земли?», то английский.

monk ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.