Showing posts with label Исключения. Show all posts
Showing posts with label Исключения. Show all posts

Friday, December 24, 2010

Обработка необработанных исключений в WinForms

Обработчик добавляется следующим образом:

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

Но этого мало. Нужно еще "включить" работу этого обработчика:

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

Причем, создать обработчик можно в любом месте кода, например, в главной форме. А вот включить его можно только до добавления главной формы, т.е. в методе
Main.

Sunday, December 12, 2010

KeyNotFoundException

В стандартной реализации исключение KeyNotFoundException сообщает что ключ не найден, но почему-то не сообщает какой именно. Исправить ситуацию можно так:

    public class MyDictionary : Dictionary<string, object>
    {

        public new object this[string key]
        {
            get
            {
                object value;
                if (!base.TryGetValue(key, out value))
                    throw new KeyNotFoundException(string.Format("Значение [{0}] не найдено", key));
                else
                    return value;
            }
            set
            {
                base[key] = value;
            }
        }

    }Теперь это исключение можно поймать и сообщить пользователю что-то осмысленное.

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

Saturday, November 27, 2010

Сообщайте информацию о коде с помощью исключений

В больших системах над кодом работают не один и часто даже не десять программистов, а значительно больше. То что знает про код один разработчик, должны знать все другие. Механизм исключений позволяет сообщить дополнительную информацию о способах использования кода и сократить время отладки.
Например, есть некий метод
public void DoAction(int param)
{
  ... некие действия...
}
Разработчик этого кода знает, что вызов этого метода с параметром -1 не допустим, т. к. он приведет к зацикливанию. Конечно, он может написать комментарий к этому методу, где он может указать эту особенность. Но где гарантия, что другой разработчик, который использует этот код, прочитает это сообщение? Вероятнее всего, в случае зацикливания, он будет отлаживать код, найдет причину зацикливания, найдет, что это происходит именно в этом методе и только потом прочитает комментарий. Время будет потеряно. А ведь ничего не стоило сделать так:
public void DoAction(int param)
{
  if (param == -1)
    throw new AgrumentException("Нельзя вызывать этот метод с -1");
  ... некие действия...
}
Теперь если кто-то попытается вызвать этот метод с параметром -1 он точно узнает, что делать этого нельзя. Две строчки кода и экономия нескольких часов времени!
Я рекомендую всегда проверять значения параметров всех public‑метдов. Это позволит избежать затрат времени ваших коллег. Вот еще несколько примеров таких проверок.
public void DoAction(object param)
{
  if (param == null)
    throw new AgrumentNullException("param");
  ... некие действия...
}
public void DoAction(string param)
{
  if  string.IsNullOrEmpty(param)
    throw new AgrumentNullException("param");
  ... некие действия...
}