Является ли "Проверка достоверности в Getter / Setter" хорошим стилем? - PullRequest
5 голосов
/ 18 ноября 2011

Мои методы Getter / Setter проверяют значение, прежде чем они устанавливают / возвращают его. Когда значение недопустимо, они выдают исключение (BadArgumentException или IllegalStateException). Это необходимо, поскольку мы инициализируем все члены с недопустимыми значениями - и поэтому мы избегаем работы с этими недопустимыми значениями (== получение ошибок / segfaults / исключение в других местах).

Преимущества:

  • Когда вы получаете значение элемента из модели, вы знаете, что они действительны
  • Проверка достоверности выполняется только в модели-объекте
  • Диапазон значений определен в модели-объекте

Это кажется очень необычным, поскольку большинство новых членов команды сначала жалуются на это - даже если они соглашаются со мной после того, как я им это объясню.

Вопрос: Это хороший стиль программирования? (Даже если это приводит к потере производительности)

Пример кода :

inline bool MyClass::HasGroupID const { return m_iGroupID != 0; }

int MyClass::GetGroupID() const
{
    if( !HasGroupID() )
        throw EPTIllegalStateException( "Cannot access uninitialized group ID!" );

    return m_iGroupID;

}   // END GetGroupID() const

void MyClass::SetGroupID( int iGroupID )
{
    // negative IDs are allowed!
    if( iGroupID != 0 )
        throw EPTBadArgumentException( "GroupID must not be zero!", iGroupID );

    m_iGroupID = iGroupID;

}   // END SetGroupID( int )

Использование

// in serialization
if( myObject.HasGroupID() )
    rStream.writeAttribute( "groupid", GetGroupID() );

// in de-serialization
try {
    // required attribute - throw exception if value is invalid
    myObject.SetGroupID( rStream.GetIntegerAttribute( "groupid" );
} catch( EPTBadArgumentException& rException )
{
    throw EPTBadXmlAttribute( fileName, rException );
}

Ответы [ 4 ]

4 голосов
/ 18 ноября 2011

Я думаю, что это общий стиль, и это, безусловно, одна из основных мотиваций для геттеров / сеттеров в первой идее. Однако, в вашем конкретном примере, я не думаю, что они имеют смысл, и я думаю, что в целом часто есть лучшие альтернативы:

Если вызов GetGroupID вызывает исключение, если HasGroupID выдает false, то это ошибка программиста - поэтому вам, вероятно, следует использовать assert. На самом деле, почему вообще возможно иметь объект без идентификатора группы? Это звучит как слегка не элегантный дизайн.

Кроме того, если допустимы только ненулевые идентификаторы, вам следует попытаться применить это ограничение еще раньше, используя выделенный тип. Таким образом, весь код, имеющий дело с идентификаторами групп, может быть уверен, что идентификаторы действительны (на самом деле, компилятор обеспечивает это!), И вы не можете забыть проверить в каком-то месте. Рассмотрим:

class GroupID {
public:
    explicit GroupID( int id ) : m_id( id ) {
        if ( m_id == 0 ) {
            throw EPTBadArgumentException( "GroupID must not be zero!", m_id );
        }
    }

    // You may want to allow implicit conversion or rather use an explicit getter
    operator int() const { return m_id; }
};

Используя это, вы можете просто написать

void MyClass::SetGroupID( GroupID id )
{
    // No error checking needed; we *know* it's valid, because it's a GroupID!
    // We can also use a less verbose variable name without losing readability
    // because the type name reveals the semantics.
    m_iGroupID = id;

} // END SetGroupID( int )

Фактически, поскольку идентификаторы групп теперь являются выделенным типом, вы можете переименовать свою функцию просто на Set и использовать перегрузки. Итак, у вас есть MyClass::Set(UserID) и MyClass::Set(GroupID) и т. Д.

1 голос
/ 18 ноября 2011

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

Обычно, в самом простом случае, ничего из этого не требуется, но в последствии появилось удивительное количество таких вещей, и, если вы возились с учениками, есть много кода, который нужно исправить. Если вы использовали простые методы получения / установки везде с первого момента, теперь вы можете добавить к ним все, что вам нужно.

0 голосов
/ 18 ноября 2011

Относительно проверки: для Сеттера вполне обычно проверять, что инварианты класса соблюдаются (действительно, это большая часть их добавленной стоимости по сравнению с публичной ценностью). Это наиболее необычно в Getters (ленивая проверка - ад для отладки), хотя в сборке Debug было бы интересно провести повторную проверку на этом этапе, чтобы просто обнаружить больше ошибок.

Однако, это не то, что вы делаете здесь. Ваш класс Invariant явно разрешает необязательный элемент (например, GroupId), но вы запрещаете пользователю устанавливать это GroupId в недопустимое состояние ... Почему!? Это нерегулярно.

Что касается Getter, существует простое решение: необязательные типы (например, Boost.Optional). Таким образом, вы всегда возвращаете без исключения, и все же вы можете предоставить значение ... или нет.

0 голосов
/ 18 ноября 2011

Я бы согласился с вашей новой командой.Для меня это не очень обычно, но если вам нужно, и это единственный способ, вы должны это сделать.Я бы попытался сделать объекты, которые заполнены соответствующими данными.Вы можете достичь этого, проверив значения в вашем конструкторе и не предлагая установщика.Сначала я извиняюсь за написание C #, но мой C ++ немного ржавый: -)

class MyClass{

    private int groupId; 

    public MyClass(int groupId) {
        if(groupId ==0){
            throw new ArgumentOutOfRangeException("groupId",groupId,"Group Id must be >0");
        }
        this.groupId = groupId;
    }

    public int GroupId{ 
        get{ 
             return groupId;
        }
    }
}

Теперь вы можете быть уверены, что Class MyClass инициализируется только в том случае, если все свойства соответствуют их требованиям.Вы можете реализовать конструктор, который принимает экземпляр MyClass и клонирует все значения для создания клонированных объектов.Если вам нужно изменить свойства, создайте интерфейс и определите MyClassChangeable

public class Programm {
    static void main(string[] args) {
        IMyClass myClassChangable = new MyClassChangable(-123);
        try {
            MyClass correctMyClass = new MyClass(myClassChangable);
            useThisClass(correctMyClass);

        } catch (Exception ex) {
            //Display to user or something
            Debug.WriteLine(ex);
        }
    }

    static void useThisClass(MyClass myClass) {
        //this is alway correct!
        int groupId = myClass.GroupId;

    }
}

interface IMyClass {
    int GroupId { get; }
}

class MyClass : IMyClass {
    private int groupId;

    public MyClass(int groupId) {
        if (groupId == 0) {
            throw new ArgumentOutOfRangeException("groupId", groupId, "Group Id must be >0");
        }
        this.groupId = groupId;
    }

    public MyClass(IMyClass instance)
        : this(instance.GroupId) {
    }

    #region Implementation of IMyClass

    public int GroupId {
        get { return groupId; }
    }

    #endregion
}


public class MyClassChangable : IMyClass {
    private int groupId = 0;

    public MyClassChangable() {

    }

    public MyClassChangable(int groupId) {
        this.groupId = groupId;
    }

    #region Implementation of IMyClass

    public int GroupId {
        get { return groupId; }
        set { groupId = value; }
    }

    #endregion
}

. Теперь вы можете использовать класс так, как хотите, и затем «проверять» его только в одной точке (такединственная точка исключения).В «BusinessCore» вы используете MyClass, который всегда имеет правильные значения.Так что никаких исключений и никаких OutOfBounds!Надеюсь, что смогу помочь, пожалуйста, свяжитесь со мной, если у вас есть дополнительные вопросы или если что-то не так, буду рад узнать!Буду рад получить обратную связь с моей идеей!

...