Передача змінної члена як параметр методу


33

У проекті я знайшов такий код:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Як переконати своїх товаришів по команді, що це поганий код?

Я вважаю, що це зайве ускладнення. Навіщо передавати змінну члена як параметр методу, якщо у вас вже є доступ до нього? Це також порушення інкапсуляції.

Чи бачите ви якісь інші проблеми з цим кодом?


21
Що змушує вас думати, що це погано?
янніс

@Yannis Rizos, ти вважаєш, що це добре? Принаймні, це зайве ускладнення. Навіщо передавати змінну як параметр методу, якщо у вас вже є доступ до неї? Це також порушення інкапсуляції.
тика

2
Дійсні бали, відредагуйте питання, щоб включити їх. Ми не можемо допомогти вам переконати своїх товаришів по команді, але ми можемо допомогти вам оцінити код, саме про це має бути питання.
янніс

8
Я вважаю за краще метод, який може підсумовувати різні змінні, ніж метод, який робить константу 2 + 2. Параметр методу призначений для повторного використання.
Данте

Я думаю, що тут важливим є тип цього параметра. Якщо це посилальний тип, я не бачу переваги, але якщо це тип значення, я думаю, що він має сенс, оскільки якщо ви модифікуєте цей тип змінної, компілятор попередить вас про те місце, де ви зламали код.
Ремі

Відповіді:


3

Я думаю, що це вагома тема, але причина, коли ви отримуєте неоднозначні відповіді, пов’язана з тим, як формувалося питання. Особисто я мав той самий досвід роботи зі своєю командою, коли передавати членів, оскільки аргументи були непотрібними і перекручували код. У нас був би клас, який працює з набором членів, але деякі функції отримують доступ до членів безпосередньо, а інші функції модифікують ті самі члени за допомогою параметрів (тобто використовують абсолютно інші назви), і для цього абсолютно не було технічних причин. Під технічною причиною я маю на увазі приклад, який надала Кейт.

Я рекомендую зробити крок назад і замість того, щоб концентруватися саме на проходженні членів як параметри, ініціювати обговорення зі своєю командою щодо чіткості та читабельності. Більш формально або просто в передпокої обговоріть, що ускладнює деякі сегменти коду легше для читання, а інші - для кодових сегментів. Потім визначте міри якості або атрибути чистого коду, до якого ви хотіли б прагнути як команда. Зрештою, навіть працюючи над проектами зеленого поля, ми витрачаємо більше 90% часу на читання, і як тільки код пишеться (скажімо, через 10-15 хвилин), він переходить до обслуговування, де читабельність стає ще важливішою.

Тож для вашого конкретного прикладу тут аргумент, який я б використав, полягає в тому, що менше коду завжди легше читати, ніж більше коду. Функція, яка має 3 параметри, мозку важче обробити, ніж функція, яка не має жодного або 1 параметра. Якщо є інша назва змінної, мозок повинен відслідковувати ще одну річ, читаючи код. Тож пам’ятайте «int m_value», а потім «int localValue» і пам’ятайте, що одне дійсно означає, що інше завжди дорожче для вашого мозку, а потім просто робота з «m_value».

Щоб отримати більше боєприпасів та ідей, я рекомендував би забрати копію чистого кодексу дядька Боба .


Захищений, побачивши вплив книги, на яку посилається.
Френк Хілеман

Хоча незначна частина мене дуже сумна, що через 5 років після написання відповіді ти просто забрав у мене 2 точки в Інтернеті, є ще одна маленька частина мене, яка цікава, якщо ти можеш надати деякі посилання, які б свідчили про (мабуть, поганий) вплив, який мала книга, на яку я посилався. Це здавалося б справедливим і майже вартує цих балів
DXM

посиланням я є сам, бачачи особисто вплив на інших розробників. Усі мотивації, що стоять за такими книгами, є гарними, однак, уточнюючи, що весь код повинен відповідати нестандартним вказівкам (тобто правилам, які фактично служать цілі), такі книги, здається, спричинить втрату критичного мислення, яке деякі трактують як культовий .
Френк Хілеман

з приводу проблем, пов'язаних з обробкою мозку, розглянемо поняття когнітивного навантаження
jxramos

30

Я можу придумати виправдання для передачі поля члена в якості параметра в (приватному) методі: він чітко визначає, від чого залежить ваш метод.

Як ви кажете, усі поля учасників - це неявні параметри вашого методу, оскільки весь об'єкт є. Однак чи справді потрібен повний об'єкт для обчислення результату? Якщо SomeMethodце внутрішній метод, який залежить тільки від цього _someField, чи не зрозуміліше зробити цю залежність явною? Насправді, явна поява цієї залежності може також натякати, що ви можете фактично переробити цей фрагмент коду з вашого класу! (Зверніть увагу, я припускаю, що тут ми не говоримо про геттерів або сеттерів, а про код, який насправді щось обчислює)

Я не став би такий самий аргумент для публічних методів, тому що абонент не знає і не піклується про те, яка частина об'єкта має значення для обчислення результату ...


2
Якою буде залишена неявна залежність? З вашого прикладу я припустив, що _someFieldце єдиний параметр, необхідний для обчислення результату, і ми просто зробили це явним. (Зауважте: це важливо. Ми не додавали залежність, ми лише зробили це явним!)
Андрес Ф.

11
-1 Якщо немає явних залежностей від членів екземпляра, то це повинен бути статичний член, який приймає значення як параметр. У цьому випадку, хоча ви придумали причину, я не думаю, що це обгрунтоване обґрунтування. Це поганий код.
Стівен Еверс

2
@SnOrfus Я насправді запропонував рефакторинг методу повністю поза класом
Андрес Ф.

5
+1. для "... чи не чистіше зробити цю залежність явною?" Abso-freaking-loutely. Хто б тут сказав: "глобальні змінні хороші як загальне правило"? Це так COBOL-68. Почуйте мене зараз і повірте пізніше. У нашому нетривіальному коді я часом інформую я про те, щоб явно передати, де використовуються змінні класового типу. У багатьох випадках ми накрутили пух за допомогою: а) довільного використання приватного поля із зворотним напрямком та його публічної власності; Тепер помножте це на 3-5 глибоких ланцюгів успадкування.
radarbob

2
@Tarion Я не згоден з дядьком Бобом з цього приводу. По можливості методи повинні бути подібними до функцій і залежати лише від явних залежностей. (Під час виклику загальнодоступних методів в OOP одна з цих залежностей є this(або self), але це стає явним самим викликом, obj.method(x)). Інші неявні залежності - це стан об'єкта; зазвичай це робить код важче зрозуміти. Коли це можливо - і в межах причини - зробіть залежності явними та функціональними. Що стосується приватних методів, якщо можливо, явно передайте всі необхідні їм параметри. І так, це допомагає виправити їх.
Андрес Ф.

28

Я бачу одну вагому причину передачі змінних членів як аргументів функції приватним методам - ​​чистоту функції. Змінні учасники фактично є глобальним станом з точки зору, що більше, змінне глобальне стан, якщо зазначений член змінюється під час виконання методу. Замінивши посилання змінних членів на параметри методу, ми можемо ефективно зробити функцію чистою. Чисті функції не залежать від зовнішнього стану та не мають побічних ефектів, завжди повертаючи однакові результати, отримуючи однаковий набір вхідних параметрів - таким чином полегшуючи тестування та подальше обслуговування.

Впевнені, що ні ваші методи не є простими, ані практичними є чистими методами на мові ООП. Але я вважаю, що ви багато отримуєте з точки зору ясності коду, використовуючи методи, які обробляють складну логіку чистими та використовують незмінні змінні, зберігаючи при цьому нечисту "глобальну" обробку станів відокремленою в межах спеціальних методів.

Передача змінних членів публічній функції одного і того ж об'єкта при зовнішньому виклику функції, проте, на мій погляд, представлятиме основний запах коду.


5
Відповідь Субур. Незважаючи на ідеалу хватів, багато класів у сьогоднішньому програмному забезпеченні є більшими та хитрішими, ніж цілі програми, коли була створена фаза "Глобали - це зло". Змінні класу для всіх практичних цілей - глобальні поля, що належать до області екземпляра класу. Наявність великої кількості роботи, виконаної в чистих функціях, забезпечує набагато більш перевірений і надійний код.
mattnz

@mattnz, чи є можливість ви надати посилання на бібліотеку Хвата або будь-яку його книгу про ідеальне програмування? Я шукав Інтернет, і нічого не можу знайти на ньому. Google продовжує намагатися автоматично виправити його на "що".
Buttle Butkus

8

Якщо функція викликається в різні часи, іноді передаючи цю змінну члена, а іноді передаючи щось інше, тоді це нормально. Наприклад, я б взагалі не вважав це поганим:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

де newStartDateє локальна змінна і m_StartDateє змінною члена.

Однак якщо функція коли-небудь викликається тільки з передаваною до неї змінною члена, це не дивно. Функції члена постійно працюють над змінними членів. Вони можуть робити це (залежно від мови, на якій ви працюєте), щоб отримати копію змінної члена - якщо це так, і ви цього не бачите, код може бути приємнішим, якщо він зробить весь процес явним.


3
Не важливо , що метод буде викликаний з іншими , ніж змінної - члена параметрів. Важливо те, що це можна було б назвати таким чином. Ви не завжди знаєте, як метод, зрештою, буде викликаний під час його створення.
Калеб

Можливо, вам слід краще замінити умову "якщо" на "needHandleIncreaseChages (newStartDate)", і тоді ваш аргумент більше не тримається.
Таріон

2

Чого ніхто не торкався - це те, що SomeMethod захищений віртуально. Це означає, що похідний клас може використовувати його І знову реалізувати його функціональність. Похідний клас не мав би доступу до приватної змінної і, таким чином, не міг забезпечити власну реалізацію SomeMethod, що залежить від приватної змінної. Замість того, щоб брати залежність від приватної змінної, декларація вимагає від абонента передати її.


Те, що вам не вистачає, це те, що ця змінна приватна особа має загальнодоступні користувачі.
tika

Отже, ви говорите, що захищений віртуальний метод повинен залежати від відкритого доступу приватної змінної? А у вас проблема з поточним кодом?
Майкл Браун

Загальнодоступні аксесуари створені для використання. Період.
tika

1

Графічні рамки графічного інтерфейсу зазвичай мають певний клас "Перегляд", який представляє речі, намальовані на екрані, і цей клас зазвичай пропонує метод, як invalidateRect(Rect r)позначити частину своєї області малювання як необхідну для перемальовки. Клієнти можуть зателефонувати до цього методу, щоб подати запит на оновлення частини перегляду. Але представлення може також називати власний метод, наприклад:

invalidateRect(m_frame);

викликати перемальовування всієї площі. Наприклад, це може зробити це, коли він вперше буде доданий до ієрархії перегляду.

У цьому немає нічого поганого - кадр подання є дійсним прямокутником, і сам погляд знає, що хоче перемалювати себе. Клас View може надати окремий метод, який не приймає жодних параметрів і використовує натомість кадр подання:

invalidateFrame();

Але навіщо додавати спеціальний метод саме для цього, коли можна використовувати більш загальний invalidateRect()? Або, якщо ви все-таки вирішили надати invalidateFrame(), ви, мабуть, реалізуєте це з точки зору більш загального invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Навіщо передавати змінну як параметр методу, якщо у вас вже є доступ до неї?

Ви повинні передавати змінні екземпляри як параметри своєму власному методу, якщо метод не працює спеціально для цієї змінної екземпляра. У наведеному вище прикладі кадр подання - це лише інший прямокутник, що стосується invalidateRect()методу.


1

Це має сенс, якщо метод є корисним методом. Скажімо, наприклад, вам потрібно отримати унікальне коротке ім’я з кількох вільних текстових рядків.

Ви не хочете кодувати окрему реалізацію для кожного рядка, натомість передача рядка звичайному методу має сенс.

Однак якщо метод завжди працює на одному члені, здається, що це трохи глухо, щоб передати його як параметр.


1

Основна причина наявності змінної члена в класі полягає в тому, щоб ви могли встановити її в одному місці та мати її значення, доступне для кожного іншого методу в класі. В цілому, ви б очікували, що немає потреби передавати змінну члена в метод класу.

Однак я можу придумати кілька причин, чому ви можете передати змінну члена в інший метод класу. По-перше, якщо вам потрібно гарантувати, що значення змінної члена потрібно використовувати незмінним при використанні з викликаним методом, навіть якщо цьому методу потрібно буде змінити фактичне значення змінної члена в якийсь момент процесу. Друга причина пов’язана з першою в тому, що ви, можливо, хочете гарантувати незмінність значення в межах ланцюга методів - наприклад, коли реалізується вільний синтаксис.

Зважаючи на це, я б не пішов так далеко, щоб сказати, що код "поганий" як такий, якщо ви передасте змінну члена одному з методів класу. Однак я б припустив, що це, як правило, не ідеально, оскільки це може заохочувати багато дублювання коду, коли параметр призначається, наприклад, локальній змінній, а додаткові параметри додають "шум" у код, де він не потрібен. Якщо ви шануєте книгу «Чистий код», ви знаєте, що в ній згадується, що кількість параметрів методу слід тримати до найменшого мінімального рівня, і лише якщо немає іншого більш розумного способу доступу до параметра .


1

Деякі речі, які приходять до мене, думаючи про це:

  1. Взагалі, підписи методів із меншою кількістю параметрів легше зрозуміти; Основною причиною придуманих методів було усунення довгих списків параметрів шляхом прив'язки їх до даних, за якими вони працюють.
  2. Якщо підпис методу залежить від змінної члена, це ускладнить зміни цих змінних у майбутньому, тому що вам потрібно не лише змінюватись всередині методу, але і скрізь, коли метод також називається. А оскільки SomeMethod у вашому прикладі захищений, підкласи також потрібно буде змінити.
  3. Методи (загальнодоступні чи приватні), які не залежать від внутрішніх класів, не повинні бути в цьому класі. Їх можна взяти за мету використовувати корисні методи та бути такими ж щасливими. У них поруч немає жодного бізнесу, який є частиною цього класу. Якщо жоден інший метод не залежить від цієї змінної після переміщення методу (ів), то і ця змінна повинна піти! Швидше за все, змінна повинна знаходитись на власному об'єкті з тим методом або методами, які діють над нею, стаючи загальнодоступними та складаючись з батьківського класу.
  4. Передача даних різним функціям, таким як ваш клас, є процедурною програмою з глобальними змінними, що просто летить перед дизайном OO. Це не зміна змінних членів та функцій членів (див. Вище), і це звучить так, ніби ваш клас не дуже згуртований. Я думаю, що кодовий запах пропонує кращий спосіб групувати свої дані та методи.

0

Ви відсутні, якщо параметр ("аргумент") є посилальним або лише для читання.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Багато розробників зазвичай призначають безпосередньо внутрішні поля змінної методами конструктора. Є деякі винятки.

Пам'ятайте, що "сетери" можуть мати додаткові методи, а не просто призначення, а іноді, навіть бути віртуальними методами або викликати віртуальні методи.

Примітка. Я пропоную зберегти внутрішні поля властивостей "захищеними".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.