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();

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

★★★★★

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

Deleted ()

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

system-root ★★★★★ ()
Ответ на: комментарий от Deleted

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

Но при этом большинство пишут сумму от 1 до n, хотя если нужна сумма до 100, то не нужна сумма до 200.

А Ларри Уолл написал целый язык, чтобы делать отчёты из текстовых файлов и при этом считается. что он правильно сделал.

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

этот вывод подводит меня к мысли о дизайне и семантике

За пределами ООП есть https://ru.wikipedia.org/wiki/YAGNI. С ним всё понятно. Появилось две задачи с общей функциональностью, выделили общий кусок в модуль. Если есть одна задача, то никаких абстракций.

А вот в ООП вроде «повторное использование кода» является почти священной коровой. Но неясны критерии, по которым код предназначен к повторному использованию.

P.S. Добавил тэг «дизайн».

monk ★★★★★ ()

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

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

А помнить, найти в документации, подключить к проекту этот класс ради класса сложнее чем заново написать.

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

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

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

Можете придумать другой достаточно короткий наглядный пример?

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

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

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

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

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

Как определяется, что «это самое переиспользование и есть аргумент» в данном случае должен быть применён (чексумма, ограничение верхней границы арифметической последовательности), а в другом случае не должен (выбор применяемой операции, форма последовательности)?

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

В общем случае имхо никак. Архитектор решает исходя из своего понимания того, что ему притащили аналитики (use cases, например). Сейчас все любят гибкость, так что потом при необходимости рефакторят.

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

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

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

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

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

Искусственный пример. Тут же нету контекста предметной области! Как можно напридумать архитектуру и определить какой код будет использоваться повторно без целеполагания на предметы реального мира (объекты бизнес интересов)? ООП этож про написание читабельного кода, написанный код должен приближаться к языку предметной области.

Новость не так давно была, в mesa решили разделить r600 от radeonsi еще сильнее чем сейчас, пока там есть общие части (повторное использование кода), но правки в смежном коде для нужд radeonsi могут что-то поломать в r600 и наоборот, видно чтоб меньше времени тратит на выявление регрессов сделали так. Обычное дело.

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

А Ларри Уолл написал целый язык, чтобы делать отчёты из текстовых файлов и при этом считается. что он правильно сделал.

PERL - Practical Exctraction and Report Lenguage. Этот акроним уже как в WINE: имеет мало значения (WINdows Emulator => WINE Is Not an Emulator). Область применения Perl уже (давным) давно за границами акронима.

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

Искусственный пример. Тут же нету контекста предметной области! Как можно напридумать архитектуру и определить какой код будет использоваться повторно без целеполагания на предметы реального мира (объекты бизнес интересов)?

ОК. Пусть будет реальный мир. Есть socket в Python, и есть socket в Java. В python в socket есть функции ntoa, aton, gethostname, ... В Java эти функции вынесены в отдельный класс InetAddress. Чей подход более верный?

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

Область применения Perl уже (давным) давно за границами акронима.

Уже да. Но в момент создания языка мотивация была: язык для формирования отчётов. Эдакий awk++. При том, что для Ларри определённо было бы быстрее написать именно те отчёты, которые ему были нужны даже на Си, чем писать целый новый язык.

monk ★★★★★ ()

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

Не знаю как для произвольного алгоритма, а вот для 1..100.reduce(+) этого адекватно не сделать. У него все части и их взаимоотношения нужные, а то, что ты собрался выносить, отношения к задаче не имеет и является исключительно побочным продуктом выбора какого-то убогого языка.

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

В python в socket есть функции ntoa, aton, gethostname, ... В Java эти функции вынесены в отдельный класс InetAddress. Чей подход более верный?

It depends. У java более низкоуровневый api, если писать удобный интерфейс для пользователей языка то у python Socket("http://localhost:8080/") читабельнее, но в последнем случае есть недостаток, скажем Socket(String) будет работать только резолвингом по протоколу DNS, а по DNSSEC? Нужно ждать новую версию python или писать свой резолвер Socket(CustomDnssecResolver.aton(serverAddress)).

Aber ★★★★ ()

Что в поставленной задаче требуется, то и обобщай.
Если никто не просил что-либо обобщать и маловероятно, что это потребуется позже, зачем тратить на это время?

blexey ★★★★★ ()

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

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

Не знаю как для произвольного алгоритма, а вот для 1..100.reduce(+) этого адекватно не сделать.

Вот примерно про это я и имел в виду. В Haskell (например) принято обобщать, если обобщение в принципе возможно. В C++/Java как-то опираются на «контекст предметной области», который похоже зависит от воображения разработчика. :-(

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

Что в поставленной задаче требуется, то и обобщай.

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

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

Нужно ждать новую версию python или писать свой резолвер Socket(CustomDnssecResolver.aton(serverAddress)).

А в java не так? Такой же Socket(customInetAddress.getByName(serverAddress)) или ждать новую версию java

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

Что-то я не то написал. Если мне посредством python понадобится только резолвить ip по имени хоста, без установления соединения, мне нужно будет пользоваться Socket? Или в питон для этого есть отдельный класс?

Вот если отдельная функциональность ответственная за резолвинг есть, то значит Socket(«host») это всего лишь дополнительный конструктор вызывающий условный Socket(InetAddress.of(«host')). Тогда в этом конкретном месте нету архитектурных различий с java, просто дополнительный конструктор в Socket для удобства.

Aber ★★★★ ()

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

Потому что это любому Васяну понятно (вдолбили ещё в школе на уроке информатики), а паттерны эта сложна. Народ до сих пор повсеместно лепит inst = new ConstantClassId() вместо inst = getInstanceFromDI() хотя про это уже даже гопники все заборы исписали.

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

Обычное дело

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

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

Что-то я не то написал. Если мне посредством python понадобится только резолвить ip по имени хоста, без установления соединения, мне нужно будет пользоваться Socket?

Да. Статическим методом (в питоне это функция пакета).

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

Не. В Java есть InetAddress.getByName(), а в python — socket.inet_aton() и socket.gethostbyname().

это всего лишь дополнительный конструктор вызывающий условный Socket(InetAddress.of(«host'))

Там кроме конструктора целая пачка функций-утилит. Включая ntohl, ntohs, htonl, htons, которых в Java, похоже, вообще нет. И sethostname, который в Java тоже совсем в другом месте.

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

Чей подход более верный?

Ясен пень, что в жабке лучше. В питоне socket это типичный god class который делает вообще всё (и за пивом бегает).

no-such-file ★★★★★ ()

Для А1, Ан, где А1 = 1, А2 = А1 + 1, ..., Ан = А(н-1) - 1, то сумма элементов есть (А1+Ан)*н/2, не нужно итерироваться, тогда логично принимать первый и последний элемент.

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

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

А есть правильный ответ «как нужно»?

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

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

то сумма элементов есть (А1+Ан)*н/2

Тогда и просто 5050 вывести можно. Если не требуется показывать кодом связь с изначальной постановкой задачи.

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

Вот это действительно жаль. Я надеялся, что есть, просто ему не всегда следуют из-за лени/незнания/ещё чего-нибудь.

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

5050

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

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

В питоне socket это типичный god class который делает вообще всё (и за пивом бегает).

О! Может ты знаешь критерий выбора! Как определить, что, например, socket.inet_aton() требует отдельного класса, а InetAddress.getLocalHost не требует?

monk ★★★★★ ()

старая проблема:

proc fac n {
    expr {$n<2? 1: $n*[fac [expr {$n-1}]]}
}

или

proc fac3 n {
    lindex {
        1 1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600
        479001600.0 87178291200.0 1307674368000.0 20922789888000.0
        355687428096000.0 6402373705728000.0
    } $n 
}

если время и задействованные вычислительные мощности не важны — делай как хочешь,
но если нужно минимизировать ресурсы — табличный/векторный метод рулит!

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

Как определить, что, например, socket.inet_aton() требует отдельного класса, а InetAddress.getLocalHost не требует?

Как будто ты не слышал про SOLID. Понятно же что приём/передача пакетов и работа с адресами - это две разные задачи и должны быть в разных классах.

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

известны случаи, когда таблицы Брадиса вбивались в код, и на целочисленной арифметике (т.е. очень быстро) решались, вполне себе, тригонометрические задачи.

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

Ясен пень, что в жабке лучше.

«У нас будет свое Socket API, с классами и классами! И еще классами!»

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

из wikipedia.org:

«Кроме немногочисленных, но очень важных достоинств метод повторного использования кода имеет ряд недостатков. Подключение к проекту сторонних библиотек автоматически приводит к необходимости контроля совместимости версий компонент создаваемой системы и версий используемых библиотек. Самым характерным примером такой ошибки считается авария ракеты-носителя «Ариан-5», вызванная использованием программного модуля, разработанного для ракеты «Ариан-4».»

«В мейнстримных языках декларируемые принципы нацелены на повышение изначально низкого для императивного программирования коэффициента повторного использования кода. В полиморфно типизированных применение концепций ООП, напротив, означает очевидное его снижение из-за перехода от параметрического полиморфизма к ad hoc полиморфизму. В динамически типизированных языках (Smalltalk, Python, Ruby) эти принципы используются для логической организации программы, и их влияние на коэффициент повторного использования трудно спрогнозировать — он сильно зависит от дисциплины программиста. Например, в CLOS мультиметоды одновременно являются функциями первого класса, что позволяет рассматривать их одновременно и как связанно квантифицированные, и как обобщённые (истинно полиморфные).»

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

Понятно же что приём/передача пакетов и работа с адресами - это две разные задачи и должны быть в разных классах.

Почему тогда преобразование адресов из одной формы в другую (InetAddress) и чтение системных настроек (getLocalHost) должны быть в одном классе? И если они должны быть в одном, то почему setLocalHost в другом?

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

Это большой обман ООП.

Вообще идея родилась вместе с первыми библиотеками. И местами хорошо работает. Например math.h.

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

Почему тогда преобразование адресов из одной формы в другую (InetAddress) и чтение системных настроек (getLocalHost) должны быть в одном классе?

Я и не говорил что в жабе всё отлично. Я сказал, что в жабе лучше чем в питоне.

то почему setLocalHost в другом?

В каком другом? Его там вообще нет, ЕМНИП.

Пакеты https://www.npmjs.com/package/left-pad и https://www.npmjs.com/package/right-pad ему соответствуют?

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

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

обман ООП

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

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

Прибивание гвоздями - это обычный рабочий процесс.

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

В каком другом? Его там вообще нет, ЕМНИП.

Да, то что нагуглилось — совсем другое...

Они же предоставляют свободные функции, а не систему ООП.

Трактуй их как класс со статическим методом. Ты же в питоне свободные функции пакета socket трактуешь как методы объекта. Иначе, если брать только методы объекта socket (accept, bind, close, connect, connect_ex, detach, dup, fileno, get_inheritable, getpeername, getsockname, getsockopt, gettimeout, ioctl, listen, makefile, recv, recvfrom, recvmsg, recvmsg_into, recvfrom_into, recv_into, send, sendall, sendto, sendmsg, sendmsg_afalg, sendfile, set_inhertable, setblocking, settimeout, setsockopt, shutdown, share), то на god object ничуть не похоже.

SOLID (а конкретно буква I) требует разбиения интерфейса до максимально возможного предела. Для статических методов в Java это должно вырождаться в один класс = один метод. То есть классы типа leftPad и rightPad.

monk ★★★★★ ()

Необходимо инкапсулировать меняющуюся сущность.

pon4ik ★★★★★ ()

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

Императивное мышление потому что. А то обдолбаются ФП и давай какие-то «вычисления» придумывать).

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