Размышления о правильном программировании |
Здравствуйте, гость ( Вход | Регистрация )
Размышления о правильном программировании |
Iron Bug |
29.3.2010, 16:58
Сообщение
#11
|
Профессионал Группа: Модератор Сообщений: 1611 Регистрация: 6.2.2009 Из: Yekaterinburg Пользователь №: 533 Спасибо сказали: 219 раз(а) Репутация: 12 |
про возвращаемое значение: а я вот категорически с этим не согласен. имхо - функция ВСЕГДА должна возвращать int с кодом ошибки и точка. результаты - в параметрах. Сам термин "функция" обязывает к математическому поведению. Как и приведено у автора статьи все проекты, которые я видела, имели именно такие функции, возвращающие int. и я думаю, что это весьма оправданно. просто когда переходишь к реальному программированию, выясняется, что иногда вообще нет смысла возвращать значение, потому что его нет как такового, а вот зато код результата выполнения нужен абсолютно во всех случаях для проверки. я не разделяю вызовы на "функции" и "процедуры" - это устаревшее условное разделение, ничем не обоснованное на практике. |
|
|
ufna |
29.3.2010, 17:22
Сообщение
#12
|
Активный участник Группа: Участник Сообщений: 362 Регистрация: 24.5.2008 Из: Курган/СПб Пользователь №: 182 Спасибо сказали: 29 раз(а) Репутация: 5 |
и положительные эмоции, и не очень
как уже было сказано - цели и практика конкретного приложения могут быть разными. Иногда "красиво" идет в ущерб быстродействию, как минимум. |
|
|
Tonal |
30.3.2010, 8:14
Сообщение
#13
|
Активный участник Группа: Участник Сообщений: 452 Регистрация: 6.12.2007 Из: Новосибирск Пользователь №: 34 Спасибо сказали: 69 раз(а) Репутация: 17 |
про возвращаемое значение: имхо - функция ВСЕГДА должна возвращать int с кодом ошибки и точка. результаты - в параметрах. Если постоянно работаешь с чистым С, и основные апи на нём или на "С с классами" то это вполне оправданно - для единообразия. В других случаях вполне разумно использовать и остальные возможности С++, например такие как возвращение экземпляра структуры/класса из функции и исключения. А вот смесь этих подходов очень чревата ошибками. Я как-то видел большой проект, где функции и возвращали код ошибки, и бросали исключения - очень неприятное спагетти получалось... Кстати, возвращать таки лучше не int, а перечисление. про геттеры и сеттеры - для крупных проектов со сложными объектами это единственный нормальный и безопасный способ обращения к данным. при этом обеспечивается полная инкапсуляция и наследование. Зависит от назначения класса/структуры. В том же std::pair геттеры и сеттеры, как и наследование бессмысленны. А наследование, в С++, обеспечивается наличием средствами языка, а не геттерами/сеттерами. Ну и про рекурсию - это маразм. Итеративные алгоритмы - это просто подмножество рекурсивных. Другое дело, что встроенную в императивный язык рекурсию можно вручную нарисовать с помощью явных стеков на массиве и циклов. Но такая эмуляция будет гораздо менее понятной и более подверженной ошибкам чем явная. С другой стороны рекурсивные алгоритмы всё-таки пока требуются не так часто. Рекурсией конечно не следует злоупотреблять, как и любой возможностью. И нужно чётко представлять во что выльется тот или иной код для выбранного языка и компилятора. П. С. Функциональные языки, типа Haskell и LISP полностью строятся на рекурсии и циклы в них - это довольно простые функции выраженные в терминах рекурсии. Ага, таки решил прочитать опус. Про рекурсию у него примерно то и написано что у меня. Про вложенные функции с ним не согласен. Просто лексическая вложенность функций ничем не лучше чем объединение их в классе или пространстве имён или модулей и пакетов. А вот для организации частичных применений/замыканий это очень удобно. Замечательно что в новом стандарте для этого есть механизм лямбд! С длинной массива тоже не всегда так однозначно. Например алгоритму бинарного поиска нужно именно отдельное количество элементов. Да, свойства я тоже не люблю - из кода не видно что происходит - тривиальное действие присвоения или что-то "великое и ужасное". Ну и вообще, красота кода сильно зависит от языка и парадигмы, в которой код написан - красивый код на Pascal будет уродским на Python и оба они будут корявыми для С++ и наоборот. Код в стиле MFC|xWidget покоробит в проекте использующем в основном на Boost/Loki. |
|
|
Iron Bug |
30.3.2010, 19:56
Сообщение
#14
|
Профессионал Группа: Модератор Сообщений: 1611 Регистрация: 6.2.2009 Из: Yekaterinburg Пользователь №: 533 Спасибо сказали: 219 раз(а) Репутация: 12 |
Да, свойства я тоже не люблю - из кода не видно что происходит - тривиальное действие присвоения или что-то "великое и ужасное". вот ради этого "великого и ужасного" и нужны сеттеры и геттеры. потому что когда у тебя закрытая библиотека и незнакомый с тонкостями твоего кода юзер хочет, скажем, написать свой класс на основе твоего базового - то вот тогда без них он пожнёт все возможные грабли. надо всегда делать библиотеки такими, чтобы вызовы были безопасны для структуры внутренних данных объекта. а прямые обращения часто этому противоречат и могут быть небезопасны при многопоточности, к примеру. а про возвращение результата функции, это элементарно: что должна вернуть функция, если ей задали параметры вне её области определения? ноль? минус один? а где гарантия, что это значение не совпадёт с нормальным возможным значением? а если кидать исключения, то, к примеру, есть у пользователя десять библиотек. в каждой - свои классы исключений, которые каждая функция кидает и надо отлавливать и разгребать это дерьмище. это просто капец, надо заметить! в сложных обработчиках исключения полезны для раскрутки стека. а вот в примитивных функциях - проще возвращать код возврата и значение в параметре. думаете, мелкософт свои стандартные библиотеки зря переписали с "безопасными" вариантами вызовов, где возвращается именно результат выполенения, а указатели или ссылки для параметров передаются в параметрах? хотя они и не пример правильного программирования, но просто так, без причины, они бы этого делать не стали. когда программируешь много и имеешь дело с разношёрстными библиотеками, нет ничего хуже, чем дикое многообразие результатов и необходимость вникать в особенности реализации каждой конкретной мелкой функции. Сообщение отредактировал Iron Bug - 30.3.2010, 20:02 |
|
|
Tonal |
31.3.2010, 7:44
Сообщение
#15
|
Активный участник Группа: Участник Сообщений: 452 Регистрация: 6.12.2007 Из: Новосибирск Пользователь №: 34 Спасибо сказали: 69 раз(а) Репутация: 17 |
Да, свойства я тоже не люблю - из кода не видно что происходит - тривиальное действие присвоения или что-то "великое и ужасное". вот ради этого "великого и ужасного" и нужны сеттеры и геттеры. потому что когда у тебя закрытая библиотека и незнакомый с тонкостями твоего кода юзер хочет, скажем, написать свой класс на основе твоего базового - то вот тогда без них он пожнёт все возможные грабли. надо всегда делать библиотеки такими, чтобы вызовы были безопасны для структуры внутренних данных объекта. а прямые обращения часто этому противоречат и могут быть небезопасны при многопоточности, к примеру. При наследовании интерфейса ты с такими граблями не столкнёшься - нет состояния у родителя. А при наследовании реализации всё сильно зависит от назначения и дизайна. Довольно часто полное закрытие внутреннего состояния от потомка приводит к необходимости его дублирования и дополнительной синхронизации. Ну и быстродействие может сильно пострадать, особенно при многопоточке. а про возвращение результата функции, это элементарно: что должна вернуть функция, если ей задали параметры вне её области определения? ноль? минус один? а где гарантия, что это значение не совпадёт с нормальным возможным значением? Ну дак давай пойдём до конца: какую сигнатуру должен иметь оператор + или new? И что они должны делать при возникновении ошибок? Что и как должен возвращать конструктор? Ну и с областью определений - есть функции внешнего интерфейса системы - там функция обязательно должна проверить корректность параметров и сообщить пользователю если что не так. А есть внутренние - передача в них неверного значения это ошибка кодирования, которую должен отловить компилятор и/или система тестирования. Для сигнализации о них вполне уместны разного рода ассерты. Т. е. такие ошибки лучше закрывать более тщательным типизированием. Ещё раз, то что хорошо для чистого С или в стиле "С с классами" как в MFC, не обязательно хорошо/удобно/наглядно/быстро/компактно в С++. а если кидать исключения, то, к примеру, есть у пользователя десять библиотек. в каждой - свои классы исключений, которые каждая функция кидает и надо отлавливать и разгребать это дерьмище. Ежели ты работаешь с зоопарком библиотек, то кто тебе сможет гарантировать одинаковые стратегии обработки ошибок в них? Даже если у некоторых стратегия схожа, то нюансы могут отличатся очень сильно. Например значение кодов возврата и их структура. Стандарты на сигнатуру - кто-то возвращает код ошибки как результат, а кто-то в параметре. У кого-то код ошибки получается вовсе вызовом отдельной функции (привет Win32 & Posix) А кто-то кидает исключение в случае ошибки а в код возврата отдаёт признак наличия предупреждений... Так что тут всяко придётся каждую отдельным местом оборачивать и обрабатывать. Думаете, мелкософт свои стандартные библиотеки зря переписали с "безопасными" вариантами вызовов, где возвращается именно результат выполенения, а указатели или ссылки для параметров передаются в параметрах? Это про С или про С++? Ну и думаю, таки зря. Сообщение отредактировал Tonal - 31.3.2010, 7:50 |
|
|
BRE |
31.3.2010, 8:03
Сообщение
#16
|
Профессионал Группа: Участник Сообщений: 1112 Регистрация: 6.3.2009 Из: Ростов-на-Дону Пользователь №: 591 Спасибо сказали: 264 раз(а) Репутация: 44 |
есть у пользователя десять библиотек. в каждой - свои классы исключений, которые каждая функция кидает и надо отлавливать и разгребать это дерьмище. это просто капец, надо заметить! К сказанному Tonal хочу добавить свои мысли по поводу зоопарка библиотек. Для интеграции каждой библиотеки наверное лучше написать свои врапперы, которые будут иметь однотипный интерфейс, как по вызову их функционала, так и по сообщению результата своей работы. Тогда возможно смена низкоуровневых библиотек без изменения кода основной системы. И даже не обязательно прятать весь функционал библиотеки, достаточно описать и предоставить только используемый. |
|
|
Iron Bug |
31.3.2010, 14:30
Сообщение
#17
|
Профессионал Группа: Модератор Сообщений: 1611 Регистрация: 6.2.2009 Из: Yekaterinburg Пользователь №: 533 Спасибо сказали: 219 раз(а) Репутация: 12 |
Ну и быстродействие может сильно пострадать, особенно при многопоточке. практика показывает, что не страдает. там, где идёт прямое обращение, компилятор сам оптимизирует вызовы в inline . так что по скорости это никак не отличается. Ну дак давай пойдём до конца: какую сигнатуру должен иметь оператор + или new? И что они должны делать при возникновении ошибок? Что и как должен возвращать конструктор? это уже демагогия. речь шла о функциях. как известно, операторы - особые функции. впрочем, за последние N лет мне ни разу не потребовалось переписывать оператор +, к примеру. просто перегрузка оператора - это довольно экзотическое требование, которое больше относится к удобству программирования, чем к эффективности. а оператор new имеет строгое формальное описание. мы тут не спорим о синтаксисе С++. Ну и с областью определений - есть функции внешнего интерфейса системы - там функция обязательно должна проверить корректность параметров и сообщить пользователю если что не так. А есть внутренние - передача в них неверного значения это ошибка кодирования, которую должен отловить компилятор и/или система тестирования. Для сигнализации о них вполне уместны разного рода ассерты. Т. е. такие ошибки лучше закрывать более тщательным типизированием. что значит "сообщить пользователю" когда, к примеру, ты управляешь системой реального времени? ругнуться в свой лог? пользы от этого - ноль. там уже накроется всё оборудование, железяки покорёжит, пока ты пишешь своё "сообщение пользователю". в реальном времени нет возможности пространного ввода-вывода и ассертов. всё решается проще - возврат 0 или 1. через регистры. очень быстро и просто. а типизирование при рантайме не помогает. пришло тебе ошибочное число от аппаратуры и надо его обрабатывать. а как ты заранее предугадаешь, что может прийти? так что далеко не везде есть возможность развёрнутых сообщений об ошибках и отлова десятков исключений с перебором, чего же там всё-таки попалось. это очень замедляет и усложняет работу софта. Ежели ты работаешь с зоопарком библиотек, то кто тебе сможет гарантировать одинаковые стратегии обработки ошибок в них? ... Стандарты на сигнатуру - кто-то возвращает код ошибки как результат, а кто-то в параметре. ну, практически все функции возвращают 0 при удачном завершении. а остальное - в параметрах. по крайней мере, хардварные программеры придерживаются такой идеологии и разнообразия мнений как-то особо не наблюдается. Сообщение отредактировал Iron Bug - 31.3.2010, 14:31 |
|
|
Tonal |
1.4.2010, 7:59
Сообщение
#18
|
Активный участник Группа: Участник Сообщений: 452 Регистрация: 6.12.2007 Из: Новосибирск Пользователь №: 34 Спасибо сказали: 69 раз(а) Репутация: 17 |
Ну и быстродействие может сильно пострадать, особенно при многопоточке. практика показывает, что не страдает. там, где идёт прямое обращение, компилятор сам оптимизирует вызовы в inline . так что по скорости это никак не отличается. Практика - показывает что может всяко получится. Например, пусть класс реализует визуальный элемент - эллипс. При изменении размера по любой из полуосей его нужно перерисовать. Наследник - круг, при изменении любой полуоси должен изменять синхронно другую. Если доступ к полуосям эллипса только через геттеры и сеттеры, которые обеспечивают перерисовку элемента при изменении, то круг неизбежно будет дёргаться. Что приведёт к уродскому интерфейсу. Другой пример - многопоточный логгер. Пишет в файл, который создаётся в конструкторе и закрывается в деструкторе. Все операции защищает мутексом. Мы хотим сделать наследника, которрый обеспечивает ротирование логов. Если мы дадим доступ к мутексу клиентам (прямой или через геттер), то и есть прямая возможность нарушить инвариант класса и получить дедлоки... А если доступ у мутексу получить нельзя, мы не сможем атомарно переоткрыть файл для организации ротирования. Причём в обоих этих случаях о каких-либо инлайнах можно забыть - всё на уровне логики. Ну дак давай пойдём до конца: какую сигнатуру должен иметь оператор + или new? И что они должны делать при возникновении ошибок? Что и как должен возвращать конструктор? это уже демагогия. речь шла о функциях. как известно, операторы - особые функции. впрочем, за последние N лет мне ни разу не потребовалось переписывать оператор +, к примеру. просто перегрузка оператора - это довольно экзотическое требование, которое больше относится к удобству программирования, чем к эффективности. а оператор new имеет строгое формальное описание. мы тут не спорим о синтаксисе С++. Демагогия - набор полемических приёмов вводящих заблуждение. Я никого в заблуждение не вводил, а предложил довести твоё утверждение до логического конца (явного противоречия) для того, чтобы выявить границы его применимости. Сигнатура операторов может изменятся в довольно широких пределах - см. например operator(). А насчёт экзотики - это опять же именно твоё личное мнение, обусловленное тем, что ты видимо мало работала в областях связанных с массовыми расчётами. У меня есть знакомая библиотека расчётов для векторной графики написанная на С++ в 94-96гг по мотивам реализаций на Fortran и Pascal. Где-то в 2003 была попытка перевода её на Delphi - провалилась. Удобство использования совмещения (перегрузки) операторов оказалась критичным. А если смотреть с точки зрения чистого С на объекты - это всего лишь чуть более удобный способ записи структуры, функций работающий с ней и таблиц указателей на такие функции. Так какая же сигнатура должна быть у конструкторов, функции std::strlen, std::string::size(), std::find, std::sort, и остальных? что значит "сообщить пользователю" когда, к примеру, ты управляешь системой реального времени? ругнуться в свой лог? пользы от этого - ноль. там уже накроется всё оборудование, железяки покорёжит, пока ты пишешь своё "сообщение пользователю". в реальном времени нет возможности пространного ввода-вывода и ассертов. всё решается проще - возврат 0 или 1. через регистры. очень быстро и просто. а типизирование при рантайме не помогает. пришло тебе ошибочное число от аппаратуры и надо его обрабатывать. а как ты заранее предугадаешь, что может прийти? так что далеко не везде есть возможность развёрнутых сообщений об ошибках и отлова десятков исключений с перебором, чего же там всё-таки попалось. это очень замедляет и усложняет работу софта. Пользователь здесь - это тот код, который вызвал функцию. Сообщить ему - это значит стандартным для твоей системы образом просигнализировать об этом. Т. е. возвратить код ошибки, или установить код операции, или вызвать исключение, или показать сообщение. В любом случае, у системы/библиотеки/модуля должен быть стандартный способ такой сигнализации. 0 и 1 через регистры это очень хорошо и быстро, а что делать, если у тебя система работает с серверами баз данных, SOAP и DCOM. И с одним из них пропала связь или возникла какая другая критическая ошибка? Критическая здесь - это когда код на данном уровне не может принять решение по её обработке самостоятельно. Какую 1 или 0 ты будешь через регистры передавать? Т. е. тебе придётся эти ошибки ка-то классифицировать и передавать о них информацию, чтобы на верхнем уровне она была доступна и её было достаточно для принятия решений. Кроме того, в случае с кодами возврата и результатом операций, у тебя нет возможность гарантировать что клиентский код не забудет их проверить и при проверке правильно проинтерпретирует. А при возбуждении исключения, ты во первых явно передаёшь нужные данные в объекте, во вторых, если исключение не обработают оно уедет на следующий уровень - т. е. нечаянно проигнорировать критическое событие не получится. Ну и "ошибочное число от аппаратуры" придти не может. Это программа может быть написана так, что она ломается, когда приходят какие-то значения. Например браузер и ОС не должны "упать" если в ответ на запрос www.microsoft.com придёт мусор из свапфайла. Это как раз внешние границы системы, только тут клиентами выступают не человек или код, а железки. И их данные нужно проверять на допустимость обязательно. А вот во внутренних функциях - все даные уже обязаны быть проверены - в ином случае это ошибка кодирования. Именно тут рулит типизация и ассерты. Ежели ты работаешь с зоопарком библиотек, то кто тебе сможет гарантировать одинаковые стратегии обработки ошибок в них? ... Стандарты на сигнатуру - кто-то возвращает код ошибки как результат, а кто-то в параметре. ну, практически все функции возвращают 0 при удачном завершении. а остальное - в параметрах. по крайней мере, хардварные программеры придерживаются такой идеологии и разнообразия мнений как-то особо не наблюдается. Хардварь - не единственная область применения С++, не говоря уже о других языках. |
|
|
AD |
1.4.2010, 8:33
Сообщение
#19
|
Профессионал Группа: Участник Сообщений: 2003 Регистрация: 4.2.2008 Из: S-Petersburg Пользователь №: 84 Спасибо сказали: 70 раз(а) Репутация: 17 |
Могу сказать, что крайне интересная тема получилась! Обоим участвующим в дискуссии большое спасибо. Крайне интересно.
|
|
|
Текстовая версия | Сейчас: 27.4.2024, 9:09 |