Як ви переходите програму з розробки на випуск?


67

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

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

Після того, як матеріал був перевірений і затверджений, потім немає сенсу переписувати його. На регулярній основі я стою з робочим «готовим» прототипом, і я отримую помилку під час тестування, і я бачу, що це результат нерозумного кодування, який є результатом всього процесу розробки. Я в середині тестування, і виправлення буде переписати ... це безлад!

Є кращі / підручникові способи, я впевнений. Але мені доводиться працювати в реальному робочому середовищі, де не все є підручником.

Тож як я переходжу свій робочий прототип до версії випуску зі стабільною базою коду? Можливо, я не повинен вважати розробку закінченою після того, як це роблю, і насправді бачу це як фазу очищення ... Не знаю, мені тут потрібна допомога.

EDIT

Хочу уточнити кілька речей.

  • Я на 100% сторона робити це безпосередньо до, а не після, код чистий і читабельний. Але мені також доведеться все робити, і я не можу мріяти про красу коду, все чисто і блискуче. Я маю знайти компроміс.

  • часто нова функція насправді є просто чимось, що ми хочемо спробувати і подивитися, чи є сенс реалізувати щось подібне. (особливо в мобільних додатках, щоб отримати реальний вигляд на фактичному пристрої) Тож це щось невелике, що (imho) не виправдовує занадто багато роботи в першій ітерації "подивимося". Однак іноді виникає питання, КОГО я плачу за цей борг? Ось у чому це питання.

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


68
Ваше запитання: "Я закопав себе в яму; як я вийду?" Стандартна відповідь - це, звичайно, перший крок, ЗАСТОСУЙТЕ ДИЗАФІНГ ДІПЕР. Ваш процес розробки можна підсумувати як "генерувати величезну кількість технічної заборгованості, а потім ігнорувати її, коли вона настане". Якщо це проблема, змініть процеси розвитку. Перевіряйте лише чистий, робочий, налагоджений, ретельно переглянений код, який відповідає його ретельно написаній специфікації. Не заборгуйте, і вам не доведеться вилазити з боргів.
Ерік Ліпперт

11
@NikkyD Якщо вам не надано часу на належну реалізацію, вам потрібно провести бесіду з вашим менеджером про вплив на якість програмного забезпечення. Зрозумійте, що всі вам тут говорять: не вкладаючи часу наперед, це шкодить вашій здатності до ефективної роботи пізніше. Ще одне питання, яке ви хочете вирішити з ними: якщо ви виходили з компанії (або "перебиралися автобусом"), новим розробникам було б надзвичайно дорого ознайомитись з кодом. Гроші, на які вони думають, що зараз вони заощаджують, обійдуться їм пізніше.
jpmc26

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

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

8
"Мені також доводиться все робити, і я не можу мріяти про красу коду, чистий і блискучий. Я повинен знайти компроміс". Компроміс означає середину, яка "достатньо добра" для обох сторін. Якщо ваш код безладний, особливо якщо він досить безладний, ви думаєте, що у вас виникнуть проблеми з його збереженням, - це не "досить добре", і вам потрібно знайти кращий компроміс.
anaximander

Відповіді:


98

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

Не знаючи, як довго щось триває, ніколи не повинно бути приводом для неохайності - навпаки. Найчистіший код - це ІМХО, який не стає вам на шляху, коли вам доведеться щось змінити. Тому моя рекомендація: завжди намагайтеся написати найчистіший код, який ви можете - особливо при кодуванні прототипу. Тому що це буде набагато простіше адаптувати, коли щось треба змінити (що, безумовно, станеться).

Не зрозумійте мене неправильно - моє розуміння "найчистішого коду" не має нічого спільного з тим, щоб зробити код красивим заради краси. Це дійсно те, що може сповільнити вас. На мій погляд, чистий код - це код, який здебільшого пояснює себе (не потрібно писати стільки документів - викликає прискорення), простий для розуміння (менше помилок, тим менше налагодження потрібно - пришвидшення, менше часу, необхідного для пошуку правильної місце для зміни - speedup), вирішує дану проблему з найменшою кількістю необхідного коду (менше коду для налагодження - очевидне прискорення), є DRY (змінити лише одне місце, коли щось потрібно змінити - speedup - та менший ризик ввести нові помилки, забуваючи змінити друге місце), дотримується стандартів кодування (менш громіздкі речі - про швидкість), використовує невеликі,

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

Робити «очищення» після цього ніколи не виходить. Подумайте про очищення перед тим, як реалізувати нову функцію або приступити до її впровадження, але не після цього. Наприклад, щоразу, коли ви починаєте торкатися до того чи іншого способу, і ви помічаєте, що він отримує довше, ніж на 10 рядків, подумайте про те, щоб перефактурувати його на більш дрібні методи - негайно , перш ніж завершити функцію. Щоразу, коли ви виявляєте наявну змінну чи ім'я функції, ви не знаєте, що саме це означає, з’ясуйте, для чого це добре, і перейменуйте річ, перш ніж робити щось інше. Якщо ви робите це регулярно, ви зберігаєте код принаймні в "досить чистому" стані. І ви починаєте економити час - адже вам потрібно набагато менше часу для налагодження.

Я в середині тестування і виправлення помилок було б переписати

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

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

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


40
@NikkyD Пропозиції Дока Брауна - це звички, які фактично скорочують час і дуже реалістичні в перспективі. Подумайте, скільки часу ви економите, якщо вам не потрібно вивчити свій код, щоб з'ясувати, як не порушувати його щоразу, коли потрібно змінити його . Виграш схожий із переходом від типу "полювання та клювання" до навчання типу дотику. Спочатку це може зайняти довше, як ви навчаєтесь, але, коли звичка з’явиться, це, безперечно, краще і принесе користь вам протягом усієї вашої кар’єри. Якщо ви вирішите не пробувати, ви ніколи не потрапите.
Даніель

44
@NikkyD: Це не роздуває часові рамки. Час було вже роздуто; ви просто не врахували приплив, коли писали програмне забезпечення і потрапляли в технічну заборгованість, на яку не планували.
Ерік Ліпперт

7
@ NikkyD: Я представляю тобі Idol with Feet of Clay та концепцію технічного боргу . Перший означає, що ви можете будувати звукове програмне забезпечення на хитких фундаментах, а другий - про "зацікавлене" (додаткова вартість), яке відчувають функції, які ви намагаєтеся застосувати, коли структура не звучить.
Маттьє М.

10
@NikkyD: ні, я пропоную написати свій код аналогічно тому, як експерт з більярду грає у свої кульки: кожен постріл виглядає просто для аутсайдера, оскільки після пострілу кульки зупиняються в положенні для нового «простого пострілу». А в більярді чи кодуванні це потребує декількох років занять ;-)
Doc Brown

16
@NikkyD: на мій досвід, коли "додавання невеликої функції вимагає багато рефакторингу", кодекс вже безлад, і потреба в "багато рефакторингу" випливає з того, що вам потрібно змінити функцію або клас, який ви робили не дотримуйтесь чистоти в минулому. Не дозволяйте, щоб справи зайшли так далеко. Але якщо ви опинилися в такій ситуації, знайдіть компроміс. Принаймні дотримуйтесь "правила boycout" і залиште код позаду в кращому стані, ніж це було до того, як ви додали функцію. Тож навіть якщо цю функцію видалити на наступному тижні, код повинен бути в кращій формі, ніж був раніше.
Док Браун

22

У вас є дві окремі проблеми, обидві з одним і тим же симптомом (неохайний код):

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

Проблема №2: Ви вважаєте, що матеріал, який ви пишете, "просто зараз" У розробці програмного забезпечення "лише зараз" код дійсно надзвичайно рідкісний. Ви самі помітили, що після задоволення вимог користувачів, суворість попиту та пропозиції дуже важко виправдати повернення та повторну реалізацію "зробленої" функції. Отже, що з цим робити? Завжди пишіть виробничий код. Для вас це функціонально означає, що ваші оцінки для зацікавлених сторін повинні бути значно більшими, щоб у вас було певного часу, щоб зробити це правильно.

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


21

Це поширена проблема - особливо, коли розробляється те, що по суті є пробною кулею програмного забезпечення як би.

Існує ряд підходів, які можуть допомогти. По-перше, підхід TDD може допомогти звести базу коду до тієї, яка є строго необхідною. Якщо ваші тести йдуть рука об руку з вашим кодом, то ви можете принаймні мати певну впевненість, що ваш код поводиться як слід.

Знайдіть час на рефактор, як їдете. Після того, як у вас є прототип, і замовник надто нетерплячий отримати його на руки, важко продати, сказати, що вам потрібно час, щоб відполірувати те, що (для них) є повним. Мені подобається щодня заїжджати, після чого слід перевірити рефактор, але YMMV.

Розробники, які швидко пишуть код, часто користуються попитом - у нас був такий розробник у моєму останньому відділі. Кожна команда хотіла його, бо він працював надто швидко. Щойно настав час тестувати та випустити його код, проте колеса швидко зійшли. Повсюдно важко кодовані речі, хакі та ярлики. Його запас незабаром впав - масово.

Вирізання виробничого коду з самого початку може здатися тяжким, але залежно від вашого оточення, існує багато інструментів, які можуть виграти розвиток, таких як Ghostdoc та Stylecop .

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


Ви маєте на увазі кожне рішення про розрив зупинки, написане колись
RubberDuck

4
Чудовий момент щодо виправдання для замовника. Я маю багато досвіду з клієнтами, які вважають, що коли графічний інтерфейс виглядає готовим, додаток також робиться. Я навчився робити графічний інтерфейс виглядом незавершеним, поки код у фоновому режимі ще не готовий, а отже, лише робити те, що клієнт бачить виглядати відшліфованим, коли код (і бізнес-логіка) є. Дуже важко пояснити, що те, що виглядає закінченим для замовника, все-таки займе місяць-два, щоб реально доставити.
Луаан

11

Постійно

Швидкість розвитку - головна причина писати чистий, читабельний і перевіряється код; це не робиться для краси, ані інших абстрактних цінностей. Чому я б заперечував це і робив це лише після цього для якогось майбутнього програміста?

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


6
+1, і з особистої точки зору мені було важко міняти передачі від злому особистих проектів вдома та написання виробничого коду на щоденній роботі. Написання професійного коду в моїх хобі-проектах виплачувало негайні дивіденди - код було легше читати, а помилок було набагато менше.
Роббі Ді

Однією з причин того, що цього ніколи не станеться, є те, що ви (краще) покращуєтесь тим, що робите з часом. Тож якщо ви зачекаєте півроку, щоб щось «почистити», ви не тільки забудете всі хвилини, необхідні для безпечного проведення прибирання, ви також будете кращим програмістом, ніж раніше, і, ймовірно, будете спокушені просто кинути все і почати спочатку. І оскільки це так багато роботи (а часто все-таки погана ідея), ви, мабуть, просто збираєтесь пропустити очищення ще раз.
Луань

"Чому я б заперечував це і робив це лише після цього для якогось майбутнього програміста?" Одкровення! І відгадайте, що? Ви іноді (а іноді, часто) є тим майбутнім програмістом.
radarbob

@RobbieDee, чудове спостереження! В інтерв’ю Малком Гладвелл - хлопець, який довів «Правила 10 000 годин» до обізнаності населення (у книзі « Аутлієри» ) - сказав, що це повинно бути «навмисною практикою» або просто витрачати час. Значення уваги на вдосконаленні, конкретизуйте практику з наміром удосконалити цей конкретний аспект навички тощо
radarbob

@ThanosTintinidis, тоді існує проблема "доброго справи не залишається безкарною". Написавши такий чистий код, хтось неминуче підкреслить його. Переконайтеся, що ви переглядаєте код, коли хтось інший торкнеться вашого чистого коду. Один простий доданий метод підірвав інкапсуляцію та узгодженість, що навіть було зафіксовано в прямому порядку . Я був лютий тиждень; і через рік, кожен раз, коли я бачу цей код.
radarbob

4

Ви робите це, розрізняючи код "Я просто намагаюся це побачити, як він працює", і код "Це спрямовано в продукт". Існує ряд способів зробити це.

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

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

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

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


1

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

Отже, відповідь - це

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

(b) намагайтеся уникати переламування речей. Не думайте довгостроково лише тоді, коли прийде час робити QA. Дійсно, вам слід весь час випробовувати кожен створений вами фрагмент та охоплювати всі вхідні випадки, ті, що також не на щасливому шляху. Патч / хак - це майже за визначенням короткострокове виправлення, яке може мати довгострокову вартість, вражаючи загальну вартість власності клієнтів у системі. Знову тиск на виведення коду, тому повинен бути баланс. Але намагайтеся не ставити короткострокові виправлення на місці, особливо. ті, які щільно з'єднують компоненти, які дійсно повинні бути вільно з'єднані. Буде проведено повторну роботу, тому робіть це СЕРЕДНО, щоб зробити це набагато простіше, щоб уникнути зламів та патчів, які з часом зберуться та стануть некерованими.


2
Лише зауваження - спритний не означає "часті зміни без думки" або "менш дизайн". Насправді я вважаю, що спритний вимагає набагато більше дизайну, ніж те, що люди зазвичай називають водоспадом. Відсутність гарного дизайну є однією з причин, коли водоспад не працює на практиці - якщо ви насправді інвестуєте в дизайн належним чином, він працює просто чудово; він також стає набагато дорожчим, ніж спритний. Якщо ви пропустите частину дизайну в гнучкому вигляді, ви просто збиваєте код вольово-невольно, і це не спрацює краще, ніж будь-яка інша практика, яка уникає дизайну.
Луаан

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

Ні, він зосереджує увагу на проектуванні менших деталей. Але в цілому ви повинні багато розробити, інакше ви просто збираєтеся виробляти жахливий продукт. Головне - зробити речі дрібними та добре розробленими, а також взаємозамінними. Якщо ви проектуєтеся менш спритно, ви робите собі (та своїм клієнтам) послугу. Короткі ітерації - це користь від використання спритного, а не необхідного або просто частини процесу - як тільки ви отримаєте все досить добре, ви можете раптом дозволити собі короткі ітерації, а не навпаки.
Луаан

Приділяти більше уваги проектуванню менших деталей є головною проблемою, коли дійсно більша картина, як правило, є найбільш істотною причиною повторної роботи. Я бачив набагато більше грошей, витрачених на раптовий бізнес: «але нам це потрібно для цього», що вимагає широкого капітального ремонту, ніж я коли-небудь бачив у зміні дизайну невеликий нещільно зв'язаний компонент
Бред Томас

Так, але до цього моменту ви вже втратили (і незалежно від того, намагалися ви спритний чи водоспад). Коли ваша «велика картина» складається з безлічі невеликих частин, які є відносно ізольованими, єдиний широкомасштабний ремонт, який ви отримуєте, - це коли вам потрібно замінити майже все. Який підхід не змушує вас втрачати все, коли потрібно починати з нуля? Навіть рівні дизайну НАСА призводять до того, що раз у раз "нам потрібно все змінити". Зберігаючи себе гнучким та пристосованим, ви отримуєте більше маневреного простору для розміщення змін, малих чи великих.
Луаан

0

Ви пишете:

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

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

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

Я думаю, ви багато чого відкладаєте "прибирання".

Моє правило:

  • Почніть з (під-) функції
  • не соромтесь писати неповні та неповні речі, можливо, деякі C & P, щоб відчути те, що я реалізую, або якщо мені доведеться подряпати останню годину кодування (зауважте, що це може йти рука об руку з TDD / тестами, це просто, що все трохи зменшено, щоб отримати швидкий зворотний зв'язок щодо простору реалізації, який я досліджую)
  • На сьогоднішній день підфункція "працює" досить добре
  • Тепер виконайте прибирання: до здійснення комітету з питань комітету .
    • Перегляньте код, щоб побачити, що очевидно
    • Зробіть різницю проти останнього зобов’язанням переглянути зміни та, можливо, усунути деякі проблеми
    • Виправте речі, які я помітив на своїй скретч-панелі
  • Тепер я виконую зобов’язання - ця якість коду готова до відправки

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

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