LINUX.ORG.RU

Степень объектной ориентированности отрицательно коррелирует с легкостью тестирования

 , ,


0

2

Всегда когда пытался постичь ООП, натыкался на то, что фиг потестишь его. Везде какое-то дурацкое ненужное состояние, надо создавать объекты, и всё такое. То ли дело функции. И вот, какая-то случайная статейка из интернета со мной согласна http://osherove.com/blog/2007/2/25/why-you-should-think-about-toop-testable-o...

Можно ли писать ООП без таких недостатков?

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

Далее, если ты внимательно посмотришь на эти типы данных... например, в ядре линукса, в файле device.h определён тип структуры device, который включает в себя указатели на другие структуры. В нём же есть указатель на функцию release, который заполняется функцией, создающей экземпляр device («инстанс»).

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

На крестах подобная запись была бы просто более лаконичной, понятной, и с контролем от некоторых ошибок (но, возможно, с неприемлемой для ядра ОС избыточностью реализации).

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

В общем, у меня отрицательное мнение об ООП сложилось после того, как я посмотрел на питоновский изврат типа ";".join(list) и javascript-овский с регекспами. Нет никакой логики в том, что функция join принадлежит именно к строке, а не, к примеру, к списку, и записывается именно так, а не list.join(";"). В ява-скрипте произвольность выбора того, к чему принадлежит метод подтверждается ещё и тем, что там одни функции работы с регекспом — это методы регекспа, а другие — методы строки (к которой применяется регексп.

А значит, она должна записываться нейтрально по отношению к аргументам, например join $list \n как в Tcl.

Зачем функции и значения пихать в одну структуру мне не очень понятно.

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

Зачем функции и значения пихать в одну структуру мне не очень понятно

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

В статьях типа «полиморфизм для начинающих» обычно приводят примеры перерисовки геометрических фигур. Если хочется чуть менее абстрактного примера - можно посмотреть на GTK+ или на win32 API. Там тоже, как и в линукс-ядре, делается ООП на неООПшном языке. Другое дело, что в ядре это, вероятно, оправдано, а вот насчёт тулкитов для создания __прикладных__ программ у меня есть большие сомнения.

В частности, именно поэтому мне Qt нравится больше, чем GTK+ - там уже всё на классах, более лаконично и понятно.

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

О нет, чтобы потестить объект, надо создавать объект!

И вот статейка из туманного прошлого не в курсе о TDD.

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

В общем, у меня отрицательное мнение об ООП сложилось после того, как я посмотрел на питоновский изврат типа ";".join(list)

Потому что ты не видишь дальше собственного носа. str.join может принимать любую (еще раз — ЛЮБУЮ) последовательность и конкатенацию надо реализовать только один раз в одном месте. А веселиться с иерархией классов и протаскивать join в базовый класс пускай будут идиотики на рубях и js.

A1
()

скорее наоборот

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

str.join может принимать любую (еще раз — ЛЮБУЮ) последовательность и конкатенацию надо реализовать только один раз в одном месте.

А где-то не так?

А веселиться с иерархией классов и протаскивать join в базовый класс

В нормальных можно реализовать join одной строчкой вроде:

let join delim seq = fold_left (fn a b -> a . delim . a) (map tostring seq) 
Или даже короче.

Никаких извращений с классами не нужно.

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

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

Для этого ООП не нужно, достаточно поддержки языком алгебраических типов данных (как в OCaml, Haskell), или просто динамической типизации (как в Tcl и вариантах Lisp).
В J и APL подобные штуки тоже очень просто делаются композицией функций.

Более конкретный пример сообщением выше.

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

Никаких извращений с классами не нужно.

Вообще-то это никак не проблема ООП, это проблема чисто-ОО-языков, в которых авторы решили, что кроме ООП в их языках ничего быть не должно.

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

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

Оно «considered unnecessary» или «considered constructible». Т.е. недостаточно нужно, чтобы непосредственно встраиваться в язык, но при необходимости может быть выражено встроенными в язык средствами.

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

А оно может быть адекватно выражено встроенными в язык средствами? Другими словами, в ржавчине уже есть delegation of implementation?

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

Без понятия, что ты считаешь адекватным и что такое delegation of implementation.

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

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

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

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

Модуль, который описывает тип regexp и функции, которые работают с ними был бы гораздо логичнее.

Осталось только завезти в JavaScript модули.

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

В общем, у меня отрицательное мнение об ООП сложилось после того, как я посмотрел на питоновский изврат типа ";".join(list) и javascript-овский с регекспами.

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

Зачем функции и значения пихать в одну структуру мне не очень понятно.

Функции в структуре нужны, чтобы гарантировать инварианты объекта как структуры данных. Функции, которым не нужно лезть в самое нутро данных, вполне могут быть вне класса. Как тот же join, ему было бы достаточно публичного интерфейса строк и списков, чтобы склеить их.

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

Осталось только завезти в JavaScript модули.

Давно пора.

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

Функции в структуре нужны, чтобы гарантировать инварианты объекта как структуры данных.

Что ты имеешь ввиду? Чтобы гарантировать, что структура данных не превратится в бессмысленную (например кто-нибудь запишет в ip-адрес 999.999.999.999 или в вероятность 1.25)? Для этого уже придуманы зависимые типы (dependent type). Зачем городить какие-то классы не понятно.

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

Для этого уже придуманы зависимые типы (dependent type). Зачем городить какие-то классы не понятно.

А зачем городить зависимые типы, когда есть классы? Я показал, зачем классы нужны там, где они есть. Вот когда будут зависимые типы в питоне, жаве или хотя бы в крестах, тогда и будем удивляться, зачем же там ещё и классы.

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

А зачем городить зависимые типы, когда есть классы?

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

Я показал, зачем классы нужны там, где они есть.

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

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

Если там будут зависимые типы, то это будет уже другой язык. Но скажи, а нафига использовать перечисленные языки, если уже сейчас есть например OCaml, Tcl и D (правда в них зависимых типов тоже нет)?

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

Кстати опять же, в функции в любом случае нужно проверять корректность исходных данных прежде чем модифицировать поля объекта, так? А значит можно их проверять в каждой функции, которая работает с типом данных и всё.

Э... Не понял. Допустим, у нас есть класс для хранения значений, ограниченных некоторыми рамками (код на C++, но в D будет практически то же самое, с поправкой на синтаксис):

template< typename T, T left, T right >
class bounded_value
{
  T value_;

  static void ensure_validity( T v ) {
    if( v < left || v > right )
      throw invalid_value{...};
  }

public :
  bounded_value( T v ) : value_{ v } {
    ensure_validity( value_ );
  }
  T query() const { return value_; }
  void set( T v ) {
    ensure_validity( v );
    value_ = v;
  }
};
Здесь мы платим за проверки только в конструкторе и в методе set. Метод query не делает никаких проверок, т.к. значение value_ нельзя изменить извне.

Если вы не хотите иметь классов, то означает ли это, что в вашем варианте bounded_value проверки нужно было бы делать и в методе query?

где без классов выполнить задачу нельзя или сложнее.

ООП появилось далеко не сразу. И до ООП софт успешно разрабатывался. ООП сделало это чуть проще.

А пример — практически любая структура данных, значения полей в которых взаимосвязаны. Например, контейнер на основе списка (или дерева, или хеш таблица), в котором для метода size() нужно обеспечить гарантию O(1). Это означает, что у вас будет список/дерево элементов контейнера отдельно, счетчик элементов отдельно. И вам нужно, чтобы значение счетчика согласовывалось с количеством элементов в контейнере.

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

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

Если вы не хотите иметь классов, то означает ли это, что в вашем варианте bounded_value проверки нужно было бы делать и в методе query?

Нет, я имел ввиду делать проверки во всех функциях (не методах!) которые возвращают T, а в тех, которые берут T как аргумент проверки не обязательны.

Например, контейнер на основе списка (или дерева, или хеш таблица), в котором для метода size() нужно обеспечить гарантию O(1).

Это может компилятор делать, например?

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

Ну зависимые типы же. Или просто тщательно следить за тем, чтобы процедуры, возвращающие/изменяющие элемент синхронизировали. Их можно выделить в отдельный модуль.

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

Это может компилятор делать, например?

Что? Обеспечивать гарантии?

Ну зависимые типы же.

B+ дерево или хитрая хеш-таблица на зависимых типах? В чем профит?

И это пока только речь шла об инкапсуляции, до полиморфизма еще даже не дошли.

Или просто тщательно следить за тем, чтобы процедуры, возвращающие/изменяющие элемент синхронизировали.

А вот с этого места поподробнее, если можно с примерами кода на каком-нибудь императивном языке.

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

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

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

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

В общем, у меня отрицательное мнение об ООП сложилось после того, как я посмотрел на питоновский изврат типа ";".join(list) и javascript-овский с регекспами.

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

Прежде всего, не надо морозить глупости самим.

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

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

Какой ещё кастомный итератор? Речь про питоновский str.join. Я говорил, что это должна была быть свободная функция, принимающая последовательность строк и строку-разделитель и возвращающая новую строку. И нечего тут вообще объектно ориентировать.

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

Прежде всего, не надо морозить глупости самим.

А вы меньше меня читайте, а то того и гляди чаем поперхнётесь.

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

А вы меньше меня читайте

Ну кто-то же должен указывать на то, что вы с Xenius морозите глупости.

а то того и гляди чаем поперхнётесь.

Не переоценивай себя.

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

Я говорил, что это должна была быть свободная функция, принимающая последовательность строк и строку-разделитель и возвращающая новую строку

Опять печалька. В питончике есть строки и байтовый строки, то есть эта функция должна уметь работать со всем? Причем в py2 был string.join.

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

Опять печалька. В питончике есть строки и байтовый строки, то есть эта функция должна уметь работать со всем?

Печалька когда ради удобства реализации корёжат интерфейс. Вот тебе функция, которая умеет работать со всем:

def join(seq, sep):
    tp = type(sep)
    it = iter(seq)
    ret = tp()

    try:
        ret += next(it)
    except StopIteration:
        pass
    else:
        for el in it:
            ret += sep
            ret += el

    return ret

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

Причем в py2 был string.join.

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

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

Если хочется иметь хардкорно оптимизированные реализации для конкретных типов строк

Конечно хочется.

можно сделать их приватными и вызывать из внешней функции.

Но зачем, когда можно позвать нужный сразу у сепаратора. Абстракции не меньше, но зато не надо думать куда приткнуть часто встречающийся join.

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