Showing posts with label Качество. Show all posts
Showing posts with label Качество. Show all posts

Thursday, March 17, 2011

Снова про code style...

Еще раз хочу про стиль кода написать. Замучился доказывать, что писать надо в едином стиле. Вот если кино начинается комедией, а заканчивается детективом, все плюются… Если в часть главы в книге написано от первого лица, а потом резко так, без перехода от третьего – странно все это выглядит. Возникает вопрос что курил автор. А если код так писать, почему-то нормально.
Второй вопрос – заглавные буквы. Заглавная буква это начало мысли, начало чего-то большого, обозначение идеи. Название городов пишут с большой буквы, имена людей и т.д. Но если взять и Просто так начать Писать часть слов Не понятно зачем писать с Большой буквы, то читать это сложно. Ну и про автора такого текста мысли какие-то бродят.. Так почему нужно писать имена переменных именно так:
  private int MyTempVar;
Почему с большой буквы-то? Имя метода (имя человека, города) – это понятно. А тут-то зачем? И читать такой код сложно, хотя можно конечно.
Вообще загадочно, почему то что в обычной жизни никто не делает, в программировании считается нормальным и до хрипоты спорят что можно и так.

Saturday, January 22, 2011

Еще раз про пользу правильного именования

В старой ветке RDSN форума обсуждалось как-то написание пословиц на C++. Вот так например:
if (flag == true)
  if (flag == true)
    if (flag == true)
      if (flag == true)
        if (flag == true)
          if (flag == true)
            if (flag == true)
              Cut();
Скороговорка:
greka.drive(river,moveType::Over);
if(greka.lookUpFirstItem(river) isinstanceof Cancer)
{
  river.insert(greka.hand);
  crayfish.grab(greka.hand);
}

И еще про рыбу:

bool IsFish(const Animal &animal)
{
    ...
    if(FishContainer.IsEmpty && IsCrawfish(animal))
        return true;
    ...
}
А вот довольно спорная по сути, но зато оформленная двумя классами:
class CBaba : public CHomoSapiens
{
public:
     bool   KonyaNaSkakuOstanovit();
     bool   VGoryazhuyIzbuVoidet();
}
class CKobila : public CAnimal
{
private:
     int    m_trudno;
public:
     bool   BabaSVozu( const CBaba&)
     {  
         m_trudno--; // Kobile legche
     }
}
Шутки-шутками, а если серьезно – подумайте, ведь правильное именование переменных и методов позволяет именно читать код, понимать его суть, т.е. бизнес-смысл! И в реальном коде должно быть тоже самое.

Friday, January 14, 2011

Split по списку разделителей

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


string[] parts = str.Split(separators);char[] separators = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '`', '~', '!', '@', '"', '#', '№', '$', ';', '%', '^', ':', '&', '?', '*', '(', ')', '-', '_', '+', '=', '|', '[', '{', ']', '}', ';', ':', '"', ',', '<', '.', '>', '?', '/', };


Если уж перечислять разделители, то просто строкой:

string[] parts = str.Split(separators.ToCharArray());string separators = " `~!@$%^&*()_+-";


Хотя конечно регулярные выражения в таких случаях могут лучше помочь.

Saturday, December 11, 2010

Антипаттерн "наивный код"

Не знаю, наверное, у этого антипаттерна есть красивое название. Смысл его в том, что программист пишет код, не задумываясь или просто надеясь, что программа будет работать в тех же условиях, что и разрабатывалась.
Вот очередной пример:
   file.Open("C:\\AutoSystem\\nastroika.dat",CFile::modeRead);
Почему собственно файл настроек должен лежать именно там? Да и вообще кто сказал, что диск C будет существовать на том компьютере, где будет устанавливаться система?

Тоже самое касается веб-варианта, когда URL вычисляется сложением
  "http://" + serverName + "/" + pageName
Запустить такой сайт через https не получится - протокол зашит в код намертво.

Бессмысленные комментарии

Даже самые несогласные соглашаются, что комментарии в коде должны быть (я уже писал зачем). Но – какой смысл писать их не понимая смысла этого действия?
Как я загнул… J Но если в тексте эта заумная фраза выглядит странно, то почему некоторые программисты считают, что аналогичные комментарии выглядят нормально:
  CStdioFile file;// объявление класса file
Правда ведь все понятно стало?
Или так:
  i++; // увеличиваем i на 1
  if (count > 0) // если больше нуля
И не лень кому-то тратить свое время на набор этих буквичек…

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 или из БД, то возможны другие исключения в этом коде – файл может быть недоступен, иметь неверный формат, в нем могут храниться не числа, числа неверной размерности и т.д. Другими словами, у нас уже есть возможный список исключений и так или иначе, нам придется их обрабатывать. А значит создавать еще один, параллельный механизм смысла не имеет и проще работать с механизмом исключений, добавив к ним свои.

Monday, December 6, 2010

Уменьшайте вложенность if-ов

Код вида

  if (условие)  {
      два листа кода
  }
  else
  {
    return false;
  }


читать крайне не удобно. Листаешь, листаешь... а тут return. Лучше писать

 if (!условие)
   return false;
 два листа кода

И читать удобнее и код направо не уезжает.

Wednesday, December 1, 2010

Какая разница между оператором as и прямым приведением типа

В C# существует два способа приведения типов: оператор as и прямое приведение типа. Отличие этих способов том, что при невозможности прямого приведения типов будет сгенерировано исключение InvalidCastException, тогда как оператор as просто вернет null.
Вполне вероятно, что после получения нулевого указателя от оператора as, где-то в коде будет сгенерировано исключение NullReferenceException (если получение null не предусматривалось логикой программы). Т. о. использование прямого приведения предпочтительнее, т. к. найти ошибку в этом случае значительно проще, чем искать безымянный NullReferenceException.