Не знаю, наверное, у этого антипаттерна есть красивое название. Смысл его в том, что программист пишет код, не задумываясь или просто надеясь, что программа будет работать в тех же условиях, что и разрабатывалась.
Вот очередной пример:
file.Open("C:\\AutoSystem\\nastroika.dat",CFile::modeRead);
Почему собственно файл настроек должен лежать именно там? Да и вообще кто сказал, что диск C будет существовать на том компьютере, где будет устанавливаться система?
Тоже самое касается веб-варианта, когда URL вычисляется сложением
"http://" + serverName + "/" + pageName
Запустить такой сайт через https не получится - протокол зашит в код намертво.
Saturday, December 11, 2010
Бессмысленные комментарии
Даже самые несогласные соглашаются, что комментарии в коде должны быть (я уже писал зачем). Но – какой смысл писать их не понимая смысла этого действия?
Как я загнул… J Но если в тексте эта заумная фраза выглядит странно, то почему некоторые программисты считают, что аналогичные комментарии выглядят нормально:
CStdioFile file;// объявление класса file
Правда ведь все понятно стало?
Или так:
i++; // увеличиваем i на 1
if (count > 0) // если больше нуля
И не лень кому-то тратить свое время на набор этих буквичек…
Friday, December 10, 2010
Наследование классов (часть 2)
Итак, у нас два класса – Door и Window и базовый для них BaseObject (см часть 1). В базовом классе есть методы Open и Close.
class BaseObject
{
private bool isOpened;
public void Open()
{
isOpened = true;
}
public void Close()
{
isOpened = false;
}
}
class Door : BaseObject
{
}
class Window : BaseObject
{
}
Мы знаем еще, что для Door при открытии и закрытии нужны дополнительные действия (т.е. методы) – открыть и закрыть дверь ключом (OpenByKey, CloseByKey). Добавить их не сложно:
class Door : BaseObject
{
public void OpenByKey()
{
}
public void CloseByKey()
{
}
}
Проблема будет в том, что при вызове метода Open нужно не забыть вызвать метод OpenByKey, иначе к нам заберутся воры J Ну а там где есть слово “нужно не забыть” – забудут наверняка, тем более, что не понятно, как подсказать программисту, использующему наш класс, что этот вызов вообще нужен.
Хорошо бы иметь свою реализацию метода Open, такую, что в ней уже вызывается работа с ключом. Получается, что с одной стороны, мы хотим использовать базовый класс и его реализацию, чтобы не дублировать код. С другой – иметь собственную реализацию этого метода.
Оказывается, сделать это очень легко – для этого используется перекрытие методов.
Метод в базовом классе мы должны обозначить как перекрываемый, добавив к нему слово virtual (метод будет называться виртуальным, но я пока не буду вдаваться в подробности почему):
class BaseObject
{
private bool isOpened;
public virtual void Open()
{
isOpened = true;
}
public virtual void Close()
{
isOpened = false;
}
}
А в классе наследнике мы должны будем обозначить, что мы перекрываем метод базового класса. Для этого используется слово override:
class Door : BaseObject
{
public override void Open()
{
base.Open();
OpenByKey();
}
public override void Close()
{
base.Close();
CloseByKey();
}
private void OpenByKey()
{
}
private void CloseByKey()
{
}
}
Thursday, December 9, 2010
Реализация треугольника
Задача заключалась в создании класса треугольник с заданными сторонами. Нужно было сделать проверку, что это действительно треугольник. Ну и плюс подсчитать его параметры.
Вот пример кода (на C++), с которым мы и будем разбираться:
class triangle
{
private:
double x,y,z;
public:
triangle()
{
x=1;y=1;z=1;
}
triangle(double a,double b,double c)
{
if (a+b>c && c+b>a && a+c>b)
{
a=x; b=y; c=z;
}
else
cout<<"please try one's more"<<endl;
}
double sumSide(double a,double b,double c)
{
return (a+b+c);
}
double square(double a,double b,double c)
{
double p=sumSide(a,b,c)/2;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
void type(double a,double b,double c)
{
if ( ( a*a > b*b+c*c ) || ( b*b > a*a+c*c ) || ( c*c > a*a+c*c ) )
cout<<"one angle is more that 90 digreas"<<endl;
else
........удалил часть кода.........
}
void angle (double a,double b,double c)
{
double alpha,betta,gamma;
alpha=acos( (b*b+c*c-a*a)/(2*(b*c)) );
betta=acos( (b*b+a*a-c*c)/(2*(b*a)) );
gamma=acos( (c*c+a*a-b*b)/(2*(c*a)) );
cout<<"alpha= "<<alpha<<endl;
cout<<"betta= "<<betta<<endl;
cout<<"gamma= "<<gamma<<endl;
}
};
int main()
{
freopen("input.txt","rt",stdin);
freopen("output.txt","wt",stdout);
double a,b,c;
cin>>a;
cin>>b;
cin>>c;
triangle firstTriangle(a,b,c);
cout<<firstTriangle.square(a,b,c)<<endl;
firstTriangle.type(a,b,c);
firstTriangle.angle(a,b,c);
return 0;
}
Что мне не нравится и вызывает вопросы:
· Почему в конструкторе создается треугольник 1/1/1? Нужен ли вообще такой конструктор? Ведь есть же тот, который сразу принимает три параметра. Мне кажется что пустой конструктор просто не нужен. Но тут зависит от задачи.
· Во втором конструкторе проверка что это не треугольник есть, но – если это не треугольник, то выводится сообщение и внутренние переменные не инициализируются вообще. Т.е. в результате получается не понятный объект с непонятными полями.
· Выводить сообщения на консоль в конструкторе и вообще в других методах – не правильно. Это не задача класса треугольник сообщать пользователю об ошибках. Это задача того класса, который пытается создать этот треугольник. А уже как он будет его выводить – другой вопрос. Может быть это вообще будет WinForms-приложение, а не консольное.
· Мне одному кажется что этот код вообще не работает и должно быть ровно наоборот: x=a; y=b; z=c; а не как сейчас?
· Зачем вообще нужны эти x,y,z если они не используются нигде, а везде суется a,b,c? По идее методы sumSize и все другие вообще не должны иметь параметров, а использовать x,y,z. См. предыдущий пункт - оно и работать тогда не будет.
· Метод type по идее должен возвращать тип треугольника, а не выводить его на консоль.
Дополнительно:
· Не проверяется что файл открыт успешно и из него можно читать
· Не проверяется что прочитано хоть что-то
· Не проверяется что файл создан и в него можно писать
Я попробую описать несколько возможных вариантов реализации.
Вариант 1 – генерация исключений в конструкторе
Этот вариант хорошо работает для C# и не очень хорошо для С++.
public class Triangle
{
private double x;
private double y;
private double z;
public Triangle(double x, double y, double z)
{
if (Check(x, y, z))
{
this.x = x;
this.y = y;
this.z = z;
}
else
{
throw new ApplicationException("Неверные параметры треугольника");
}
}
private bool Check(double x, double y, double z)
{
// проверяем все что надо и возвращаем true/false
// если надо – можем тут кинуть дополнительные исключения
}
}
В этом случае создать некорректный объект просто не получится, о чем мы и сообщим тому, кто будет создавать:
try
{
Triangle t = new Triangle(10, 10, 10);
}
catch (Exception ex)
{
Console.WriteLine("Не удалось создать треугольник с такими параметрами. Причина: " + ex.Message);
}
Причем ловить это исключение нужно в том месте, где мы готовы его обработать. В данном случае – в основной программе.
Вариант 2. Статический метод
Если конкретная причина почему не удалось создать объект треугольника нас не интересует, то можно добавить статический метод:
public class Triangle
{
..............................
public static Triangle TryCreateTriangle(double x, double y, double z)
{
try
{
return new Triangle(x, y, z);
}
catch
{
return null;
}
}
}
И тогда просто проверяем получилось или нет:
Triangle t1 = Triangle.TryCreateTriangle(10, 10, 10);
if (t1 != null)
{
// создали и можно с ним работать
}
Но это очень редко бывает и в самых простых случаях, типа int.TryParse(). В большинстве случает так делать не стоит, т.к. здесь глушатся совершенно все исключения и найти причину ошибки будет сложно.
Вариант 3. Тип треугольника
Еще вариант – делаем перечисление:
public enum TriangleType
{
Error = 0,
OneMore90 = 1,
OneEqual90 = 2,
AllLess90 = 3
}
В конструкторе проверяем и вычисляем все нужные поля и тип треугольника:
public Triangle(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
this.type = GetTriangleType();
}
В методе GetTriangleType делаем и проверки и вычисления типа.
А при создании просто проверяем тип – если он не Error, то все хорошо и треугольник какой-никакой, но получился. Т.к. кроме наших собственных исключений у нас больше тут ничего не планируется, то их можно и не делать и обойтись этим вариантом.
Triangle t2 = new Triangle(10, 10, 10);
if (t1.Type == TriangleType.Error)
{
Console.WriteLine("все плохо");
}
Какой вариант выбрать
Выбор варианта зависит от конкретной задачи. Если вам на выбор предложат стол или стул, то первый вопрос будет – “для чего?” Чтобы определиться с конкретной реализацией (здесь я привел всего три варианта, а их можно придумать и больше), нужно понимать в каких условиях это будет работать, в каком месте кода и т.д.
Прежде всего нужна постановка бизнес-задачи. Т.е. задача должна формулироваться с точки зрения пользователя реализуемой программы, а не просто “напишите класс треугольник”. Написать “просто” сложно – слишком много вариантов. Конечно тот вариант, который я показал в начале вообще не проходит. Но и нормальных вариантов реализации очень много.
Что должно происходить, если не удалось создать объект? Просто сообщить об этом пользователю? Попросить пользователя ввести данные повторно? Данные вводит пользователь или они читаются из файла? В файле хранится один треугольник или их список?
Нужен ли вариант с генерацией исключений? Вообще говорят, в таком простом коде – нет. Исключения очень ресурсоемкие и их использование существенно усложняет и замедляет код. Если других исключений, кроме наших собственных не планируется, то можно обойтись и без исключений, например, с помощью варианта с типом треугольника, а причину ошибки, если она есть – хранить в специальном поле.
Если код будет сложнее, например, данные будут читаться из XML или из БД, то возможны другие исключения в этом коде – файл может быть недоступен, иметь неверный формат, в нем могут храниться не числа, числа неверной размерности и т.д. Другими словами, у нас уже есть возможный список исключений и так или иначе, нам придется их обрабатывать. А значит создавать еще один, параллельный механизм смысла не имеет и проще работать с механизмом исключений, добавив к ним свои.
Tuesday, December 7, 2010
Наследование классов (часть 1)
Предположим, что у нас есть два объекта – Door (дверь) и Window (окно). Каждый из них имеет методы Open (открыть) и Close (закрыть):
Class Door
{
private bool isOpened;
public void Open()
{
isOpened=true;
}
public void Close()
{
isOpened=false;
}
}
Точно такой же код содержится в классе Window. Проблема именно в “точно такой же” – при необходимости модифицировать этот код нам придется найти все его вхождения и исправить. Не факт что мы найдем все.
Наследование позволяет, во-первых, избавиться от дублирования кода, вынести его в общее место.
class BaseObject
{
private bool isOpened;
public void Open()
{
isOpened=true;
}
public void Close()
{
isOpened=false;
}
}
class Door : BaseObject
{
// методы Open и Close доступны
// они придут сюда из базового объекта
}
class Window : BaseObject
{
}
Во-вторых, наследование выстраивает логические связи между объектами, облегчая их понимание. Вместо кучи разрозненных классов, мы получаем осмысленную структуру (иерархию классов). Разумеется, эта иерархия должна отражать логические связи, иначе, она будет не понятна. Например, класс Бутылка тоже может иметь методы Открыть и Закрыть, но наследовать его от базового объекта СтроительныеМатериалы, не очень логично.
Класс BaseObject, от которого происходит наследование, называется базовым классом.
Классы Door и Window называются наследниками (потомками) класса BaseObject.
Замечу, что в C++ и многих других языках, можно было реализовать класс, наследующий несколько классов, но в C# такую возможность закрыли. Сделали это специально, т.к. множественное наследование вызывало ошибки.
В C# абсолютно все типы наследуются от специального типа System.Object.
Итак, первые два плюса наследования:
1. Сокращение дублирования кода
2. Создание логических связей между объектами.
Продолжение следует.
Monday, December 6, 2010
Уменьшайте вложенность if-ов
Код вида
if (условие) {
два листа кода
}
else
{
return false;
}
читать крайне не удобно. Листаешь, листаешь... а тут return. Лучше писать
if (!условие)
return false;
два листа кода
И читать удобнее и код направо не уезжает.
if (условие) {
два листа кода
}
else
{
return false;
}
читать крайне не удобно. Листаешь, листаешь... а тут return. Лучше писать
if (!условие)
return false;
два листа кода
И читать удобнее и код направо не уезжает.
Subscribe to:
Posts (Atom)