Pull to refresh

Comments 21

Именно поэтому я не понимаю принципа TDD, т.е. описать архитектуру проекта до написания его реализации. Ведь именно во время написания реализации и возникают моменты требующие рефакторинга и изменения архитектуры
У вас неверное понимание что такое TDD.
Мне недавно посоветовали книгу «Test Driven Development by example». Автор Кент Бек.
Я тоже не понимал принципа ТДД, после прочтения же — прозрел в каком-то смысле.
Вы, наверное, в сообщении ошиблись. Или перепутали TDD с водопадом.
UFO just landed and posted this here
UFO just landed and posted this here
Начал читать и возникло ощущение дежавю. А потом увидел, что автор то тот же что и здесь ))
За статью спасибо.
>Хороший дизайн – это дизайн, который вы сможете объяснить своему коллеге за 10 минут, не жертвуя при этом полнотой или точностью

Так как же люди умудряются строить атомные электростанции?
Принцип работы станции легко объясняется за 5 минут.
Да, на уровне «атомная станция — это огромный кипятильник».

Только вот легче от этого не становится.
Хорошая статья. Но я бы еще усилил ее тем, что для выбора лучшего решения вырабатываются определенные правила у программиста и он не выбирает интуитивно или следуя неясной красоте.

Конечно, всегда давят много факторов для выбора того или иного решения. Так, на вскидку, по приоритетам, какие у меня правила:
1. Код описывает задачу или реализацию? Декларативное описание самой задачи лучше хитрых алгоритмов.
2. Код является самым простым решением?
3. Код требует комментариев? Если да — код плохой. Нужно подумать, как средствами языка выразить яснее мысль.
4. Количество кода. Чем меньше, тем лучше.
ну и далее, примерные вопросы рефакторинга:
5. Есть ли в коде повторения?
6. Есть ли в данных повторения?
7. Есть ли магические числа?
И т.д.

Кое-какие пункты могут противоречить друг другу. Нужны приоритеты для выбора.

На счет ковариантности. Имхо, вещь оправданная. Не знаю, с каких соображений точно майкрософт ее ввел. Но я как-то делал свою ORM и реализацию MVP паттерна. Грешил на досуге. Пытался сделать общее хорошее решение. И мне надо было передавать на ГУИ либо сущности, либо списки. Без ковариантости список нельзя привести с списку базового и пришлось бы передавать object. А потом надеяться, что там будет список сущностей. Плохо.

А проблема типизации и без ковариантности существует. Вот пример:

string s = "1";
object o = s;
int a = (int)o;


Так устроено на шарпе ООП. Да и вообще это глобальная проблема. Статическая типизация — это то, что ограничивает и делает код негибким. Негибкость — специальная и хорошая вещь, которая сама проверяет, чтобы баги не возникали. Это рельсы для поезда. Если они есть, он не ездит там где не положено. А способы увеличения гибкости (полиморфизм на виртуальных методах) — это вещь противоположная. Оно нарушает хоть немного статическую типизацию.
Привели к базовому — потеряли информацию, что это за тип. Захотели привести к наследнику, ошиблись, привели не к тому.
Создали ссылку в классе на делегат. Код стал гибким. В рантайме можно подключить любой обработчик. Но не установили ссылку — приложение в рантайме падает. И статической типизацией никак не помочь.

Так что ковариантность — нормально.
>Привели к базовому — потеряли информацию, что это за тип. Захотели привести к наследнику, ошиблись, привели не к тому.

Так уж и потеряли? Для этого и существуют всякие «instanceof», «isinstance», да хоть те же ClassCastExceptions на худой конец.

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

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

От этого типизация не перестаёт быть статической. В той же Java замечательно работает instanceof, но не хотите же вы в самом деле назвать её динамически типизированным языком?
Перестает. То, что язык статически типизирован, значит, что он в определенных пределах может проверить при компиляции. Но есть и много средств эту типизацию нарушить или обойти.

Если вы передаете в метод параметр типа int, то в методе можете смело надеяться, что туда передали целый тип и пользоваться им. Это статическая типизация. Если же вы в метод передаете тип object, но знаете, что там будет int и далее либо надеетесь на это, либо проверяете, бросаете эксепшин, либо еще что-то делаете — это уже не статическая типизация. Суть в том, что если вы ошиблись в программе и туда таки не передается int, то во время компиляции вы об этом не узнаете. Узнаете когда выполнение программы зайдет в этот метод.
Ну извините, по вашей логике получается, что и C не является статически типизированным языком из-за наличия и широкого использования void*.

Пример: с pthread когда-нибудь работали? Что там передаётся в качестве параметра? Правильно, void*. И ситуация точь-в-точь повторяет то, что вы только что сказали:
… в метод передаете тип void*, но знаете, что там будет int и далее либо надеетесь на это, либо проверяете, бросаете эксепшин, либо еще что-то делаете (ну понятно, что в случае C ни эксепшена, ни проверки не получится, но «знаем либо надеемся» — это да, это по полной программе) — это уже не статическая типизация. (Вот ведь новости — C не являеся статически типизированным!) Суть в том, что если вы ошиблись в программе и туда таки не передается int, то во время компиляции вы об этом не узнаете. Узнаете когда выполнение программы зайдет в этот метод."

Да, именно так и будет — узнаем только когда исполнение зайдёт в этот метод (и громко навернётся, если тип не тот).

Поздавляю вас с открытием! Хотя может вам всё же стоит пересмотреть своё понимание статической/динамической типизации в языках программирования?
Может вам надо пересмотреть?

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

Просто по определению — динамическая типизация, это когда тип узнают во время выполнения.
По-моему мы изначально спорим о разных вещах, поэтому разногласия начинаются на этапе определений.
Тип передаваемой переменной (Object ли, void* ли) проверяется на этапе компиляции. И когда туда передаётся что-то другое, оно сначала кастится к Object или void*, а уже потом передаётся, и компилятор следит за тем, чтобы передавалось именно то, что требуется (т.е. Object или void*) — и это принципиальный момент! Да, компилятор можно «обмануть», скастовав до нужного, но кастовать таки нужно, ибо компилятор таки следит за типом.
В динамически типизированных языках ни хрена не проверяется, и кастовать ничего не нужно, и в функцию, ожидающую int, можно передать dict, и никто и не почешется до момента выполнения.
Когда вы приводите к типу указатель на войд, то потом только в рантейме вы можете узнать реальный тип. Это и есть динамическая типизация.

Вообще, приведение к предку — нормально. Если будут использоваться только у предка методы. А вот приведение назад к потомку — самая настоящая динамическая типизация.
Компилятор уже не следит за этим. Он не может узнать во время компиляции, что вы там передаете.

Кстати, я как-то на С писал транслятор одного динамически типизированного языка. Интерпретатор. Так по сути это оно и есть. Я указатели на войд кругом передавал. А сам интерпретатор уже разруливал в рантайме что и как. Если бы С такого не позволял, то даже представить тяжело, как бы на нем можно было написать интерпретатор динамически типизированного языка.

То, что компилятор следит, чтобы передавали именно это — указатель на войд и т.д. — он отслеживает. В этом смысле тут статическая типизация. Но этого не достаточно. Т.к. реальный тип неизвестен. И кастуют его в рантайме.

Да вообще, просто если вы в рантайме делаете такие манипуляции, узнавая тип — это по определению динамическая типизация. А если вы разработали такую систему типов в программе, когда такое не требуется — вы хорошо продумали типы, чтобы использовать на всю статическую типизацию языка. И последний вариант в большинстве случаев лучший.
>Когда вы приводите к типу указатель на войд, то потом только в рантейме вы можете узнать реальный тип. Это и есть динамическая типизация.

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

>Если бы С такого не позволял, то даже представить тяжело, как бы на нем можно было написать интерпретатор динамически типизированного языка.

Какое отношение имеет типизация «целевого» языка к типизации языка, на котором написан интерпретатор? о_О
Я тоже лет 10 назад писал интерпретатор, и void* вообще не использовал.

Короче, продолжать бессмыссленно — как я уже сказал, мы оперируем разными понятиями.
Качество кода определяется очень легко: если работает — все ОК :)

Софт с хорошим дизайном легко тестируется, а с хорошей архитектурой — маштабируется.

все просто
Sign up to leave a comment.

Articles