Hibernate. Основные принципы работы с сессиями и транзакциями. Спрашивали? Отвечаем - Школа MODX

Если вы.NET программист, то вы наверняка объявляли и использовали события в своем коде. Несмотря на это, не все знают, как события работают внутри и какие особенности связаны с их применением. В этой статье я попытался описать работу событий как можно более подробно, включая некоторые частные случаи, с которыми редко приходится иметь дело, но про которые важно и\или интересно знать.

Что такое событие?

Событием в языке C# называется сущность, предоставляющая две возможности: для класса - сообщать об изменениях, а для его пользователей - реагировать на них.
Пример объявления события:

Public event EventHandler Changed;
Рассмотрим, из чего состоит объявление. Сначала идут модификаторы события, затем ключевое слово event, после него - тип события, который обязательно должен быть типом-делегатом, и идентификатор события, то есть его имя. Ключевое слово event сообщает компилятору о том, что это не публичное поле, а специальным образом раскрывающаяся конструкция, скрывающая от программиста детали реализации механизма событий. Для того, чтобы понять, как работает этот механизм, необходимо изучить принципы работы делегатов.

Основа работы событий - делегаты

Можно сказать, что делегат в.NET - некий аналог ссылки на функцию в C++. Вместе с тем, такое определение неточно, т.к. каждый делегат может ссылаться не на один, а на произвольное количество методов, которые хранятся в списке вызовов делегата (invocation list). Тип делегата описывает сигнатуру метода, на который он может ссылаться, экземпляры этого типа имеют свои методы, свойства и операторы. При вызове метода Invoke() выполняется последовательный вызов каждого из методов списка. Делегат можно вызывать как функцию, компилятор транслирует такой вызов в вызов Invoke().
В C# для делегатов имеются операторы + и -, которые не существуют в среде.NET и являются синтаксическим сахаром языка, раскрываясь в вызов методов Delegate.Combine и Delegate.Remove соответственно. Эти методы позволяют добавлять и удалять методы в списке вызовов. Разумеется, форма операторов с присваиванием (+= и -=) также применима к операторам делегата, как и к определенным в среде.NET операторам + и - для других типов. Если при вычитании из делегата его список вызовов оказывается пуст, то ему присваивается null.
Рассмотрим простой пример:

Action a = () => Console.Write("A"); //Action объявлен как public delegate void Action(); Action b = a; Action c = a + b; Action d = a - b; a(); //выведет A b(); //выведет A c(); //выведет AA d(); //произойдет исключение NullReferenceException, т.к. d == null

События - реализация по умолчанию

События в языке C# могут быть определены двумя способами:
  1. Неявная реализация события (field-like event).
  2. Явная реализация события.
Уточню, что слова “явная” и “неявная” в данном случае не являются терминами, определенными в спецификации, а просто описывают способ реализации по смыслу.

Рассмотрим наиболее часто используемую реализацию событий - неявную. Пусть имеется следующий исходный код на языке C# 4 (это важно, для более ранних версий генерируется несколько иной код, о чем будет рассказано далее):

Class Class { public event EventHandler Changed; }
Эти строчки будут транслированы компилятором в код, аналогичный следующему:

Class Class { EventHandler сhanged; public event EventHandler Changed { add { EventHandler eventHandler = this.changed; EventHandler comparand; do { comparand = eventHandler; eventHandler = Interlocked.CompareExchange(ref this.changed, comparand + value, comparand); } while(eventHandler != comparand); } remove { EventHandler eventHandler = this.changed; EventHandler comparand; do { comparand = eventHandler; eventHandler = Interlocked.CompareExchange(ref this.changed, comparand - value, comparand); } while (eventHandler != comparand); } } }
Блок add вызывается при подписке на событие, блок remove - при отписке. Эти блоки компилируются в отдельные методы с уникальными именами. Оба этих метода принимают один параметр - делегат типа, соответствующего типу события и не имеют возвращаемого значения. Имя параметра всегда ”value”, попытка объявить локальную переменную с таким именем приведет к ошибке компиляции. Область видимости, указанная слева от ключевого слова event определяет область видимости этих методов. Также создается делегат с именем события, который всегда приватный. Именно поэтому мы не можем вызвать событие, реализованное неявным способом, из наследника класса.

Interlocked.CompareExchange выполняет сравнение первого аргумента с третьим и если они равны, заменяет первый аргумент на второй. Это действие потокобезопасно. Цикл используется для случая, когда после присвоения переменной comparand делегата события и до выполнения сравнения другой поток изменяет этот делегат. В таком случае Interlocked.CompareExchange не производит замены, граничное условие цикла не выполняется и происходит следующая попытка.

Объявление с указанием add и remove

При явной реализации события программист объявляет делегат-поле для события и вручную добавляет или удаляет подписчиков через блоки add/remove, оба из которых должны присутствовать. Такое объявление часто используется для создания своего механизма событий с сохранением удобств языка C# в работе с ними.
Например, одна из типичных реализаций заключается в отдельном хранении словаря делегатов событий, в котором присутствуют только те делегаты, на события которых была осуществлена подписка. Доступ к словарю осуществляется по ключам, которыми обычно являются статические поля типа object, используемые только для сравнения их ссылок. Это делается для того, чтобы уменьшить количество памяти, занимаемое экземпляром класса (в случае, если он содержит большое количество нестатических событий). Эта реализация применяется в WinForms.

Как происходит подписка на событие и его вызов?

Все действия по подписке и отписке (обозначаются как += и -=, можно легко спутать с операторами делегатов) компилируются в вызовы методов add и remove. Вызовы внутри класса, отличные от вышеуказанных, компилируются в простую работу с делегатом. Следует заметить, что при неявной (и при правильной явной) реализации события невозможно получить доступ к делегату извне класса, работать можно лишь с событием как с абстракцией - подписываясь и отписываясь. Так как нет способа определить, подписались ли мы на какое-либо событие (если не использовать рефлексию), то кажется логичным, что отписка от него никогда не вызовет ошибок - можно смело отписываться, даже если делегат события пуст.

Модификаторы событий

Для событий могут использоваться модификаторы области видимости (public, protected, private, internal), они могут быть перекрыты (virtual, override, sealed) или не реализованы (abstract, extern). Событие может перекрывать событие с таким же именем из базового класса (new) или быть членом класса (static). Если событие объявлено и с модификатором override и с модификатором abstract одновременно, то наследники класса должны будут переопределить его (равно как и методы или свойства с этими двумя модификаторами).

Какие типы событий бывают?

Как уже было отмечено, тип события всегда должен быть типом делегата. Стандартными типами для событий являются типы EventHandler и EventHandler где TEventArgs - наследник EventArgs. Тип EventHandler используется когда аргументов события не предусмотрено, а тип EventHandler - когда аргументы события есть, тогда для них создается отдельный класс - наследник от EventArgs. Также можно использовать любые другие типы делегатов, но применение типизированного EventHandler выглядит более логичным и красивым.

Как все обстоит в C# 3?

Реализация field-like события, которая описана выше, соответствует языку C# 4 (.NET 4.0). Для более ранних версий существуют весьма существенные отличия.
Неявная реализация использует lock(this) для обеспечения потокобезопасности вместо Interlocked.CompareExchange с циклом. Для статических событий используется lock(typeof(Class)). Вот код, аналогичный раскрытому компилятором неявному определению события в C# 3:

Class Class { EventHandler changed; public event EventHandler Changed { add { lock(this) { changed = changed + value; } } remove { lock(this) { changed = changed - value; } } } }
Помимо этого, работа с событием внутри класса ведется как с делегатом, т.е. += и -= вызывают Delegate.Combine и Delegate.Remove напрямую, в обход методов add/remove. Это изменение может привести к невозможности сборки проекта на языке C# 4! В C# 3 результатом += и -= был делегат, т.к. результатом присвоения переменной всегда является присвоенное значение. В C# 4 результатом является void, т.к. методы add/remove не возвращают значения.

Помимо изменений в работе на разных версиях языка есть еще несколько особенностей.

Особенность №1 - продление времени жизни подписчика

При подписке на событие мы добавляем в список вызовов делегата события ссылку на метод, который будет вызван при вызове события. Таким образом, память, занимаемая объектом, подписавшимся на событие, не будет освобождена до его отписки от события или до уничтожения объекта, заключающего в себе событие. Эта особенность является одной из часто встречаемых причин утечек памяти в приложениях.
Для исправления этого недостатка часто используются weak events, слабые события. Эта тема уже .

Особенность №2 - явная реализация интерфейса

Событие, являющееся частью интерфейса, не может быть реализовано как поле при явной реализации этого интерфейса. В таких случаях следует либо скопировать стандартную реализацию события для реализации как свойство, либо реализовывать эту часть интерфейса неявно. Также, если вам не нужна потокобезопасность этого события, можно использовать самое простое и эффективное определение:

EventHandler changed; event EventHandler ISomeInterface.Changed { add { changed += value; } remove { changed -= value; } }

Особенность №3 - безопасный вызов

События перед вызовом следует проверять на null, что следует из описанной выше работы делегатов. От этого разрастается код, для избежания чего существует как минимум два способа. Первый способ описан Джоном Скитом (Jon Skeet) в его книге C# in depth :

Public event EventHandler Changed = delegate { };
Коротко и лаконично. Мы инициализируем делегат события пустым методом, поэтому он никогда не будет null. Вычесть из делегата этот метод невозможно, т.к. он определен при инициализации делегата и у него нет ни имени, ни ссылки на него из любого места программы.

Второй способ заключается в написании метода, содержащего в себе необходимую проверку на null. Этот прием особенно хорошо работает в.NET 3.5 и выше, где доступны методы расширений (extension methods). Так как при вызове метода расширений объект, на котором он вызывается, является всего лишь параметром этого метода, то этот объект может быть пустой ссылкой, что и используется в данном случае.

Public static class EventHandlerExtensions { public static void SafeRaise(this EventHandler handler, object sender, EventArgs e) { if(handler != null) handler(sender, e); } public static void SafeRaise(this EventHandler handler, object sender, TEventArgs e) where TEventArgs: EventArgs { if(handler != null) handler(sender, e); } }
Таким образом, мы можем вызывать события как Changed.SafeRaise(this, EventArgs.Empty), что экономит нам строчки кода. Также можно определить третий вариант метода расширений для случая, когда у нас EventArgs.Empty, чтобы не передавать их явно. Тогда код сократится до Changed.SafeRaise(this), но я не буду рекомендовать такой подход, т.к. для других членов вашей команды это может быть не так явно, как передача пустого аргумента.

Тонкость №4 - что не так со стандартной реализацией?

Если у вас стоит ReSharper, то вы могли наблюдать следующее его сообщение . Команда решарпера правильно считает, что не все ваши пользователи достаточно осведомлены в работе событий\делегатов в плане отписки\вычитания, но тем не менее ваши события должны работать предсказуемо не для ваших пользователей, а с точки зрения событий в.NET, а т.к. там такая особенность есть, то и в вашем коде она должна остаться.

Бонус: попытка Microsoft сделать контравариантные события

В первой бете C# 4 Microsoft попытались добавить контравариантности событиям. Это позволяло подписываться на событие EventHandler методами, имеющими сигнатуру EventHandler и все работало до тех пор, пока в делегат события не добавлялось несколько методов с разной (но подходящей) сигнатурой. Такой код компилировался, но падал с ошибкой времени выполнения. По всей видимости, обойти это так и не смогли и в релизе C# 4 контравариантность для EventHandler была отключена.
Это не так заметно, если опускать явное создание делегата при подписке, например следующий код отлично скомпилируется:

Public class Tests { public event EventHandler Changed; public void Test() { Changed += ChangedMyEventArgs; Changed += ChangedEventArgs; } void ChangedMyEventArgs(object sender, MyEventArgs e) { } void ChangedEventArgs(object sender, EventArgs e) { } }
Это происходит потому, что компилятор сам подставит new EventHandler(...) к обеим подпискам. Если же хотя бы в одном из мест использовать new EventHandler(...), то компилятор сообщит об ошибке - невозможно сконвертировать тип EventHandler в EventHandler (здесь Events - пространство имен моего тестового проекта).

Источники

Далее приведен список источников, часть материала из которых была использована при составлении статьи. Рекомендую к прочтению книгу Джона Скита (Jon Skeet), в которой в деталях описаны не только делегаты, но и многие другие средства языка.
2.
3.
4.
5. Ожидания
6.
7. WebDriver API
8. Приложение: Часто Задаваемые Вопросы

5. Ожидания

В наши дни большинство веб-приложений используют AJAX технологии. Когда страница загружена в браузере, элементы на этой странице могут подгружаться с различными временными интервалами. Это затрудняет поиск элементов, если элемент не присутствует в DOM , возникает исключение ElementNotVisibleException. Используя ожидания, мы можем решить эту проблему. Ожидание дает некий временной интервал между произведенными действиями - поиске элемента или любой другой операции с элементом.

Selenium WebDriver предоставляет два типа ожиданий - неявное (implicit) и явное (explicit). Явное ожидание заставляет WebDriver ожидать возникновение определенного условия до произведения действий. Неявное ожидание заставляет WebDriver опрашивать DOM определенное количество времени, когда пытается найти элемент.

5.1 Явные ожидания

Явное ожидание - это код, которым вы определяете какое необходимое условие должно произойти для того, чтобы дальнейший код исполнился. Худший пример такого кода - это использование команды time.sleep(), которая устанавливает точное время ожидания. Существуют более удобные методы, которые помогут написать вам код, ожидающий ровно столько, сколько необходимо. WebDriverWait в комбинации с ExpectedCondition является одним из таких способов.

From selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Firefox() driver.get("http://somedomain/url_that_delays_loading") try: element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "myDynamicElement"))) finally: driver.quit()
Этот код будет ждать 10 секунд до того, как отдаст исключение TimeoutException или если найдет элемент за эти 10 секунд, то вернет его. WebDriverWait по умолчанию вызывает ExpectedCondition каждые 500 миллисекунд до тех пор, пока не получит успешный return. Успешный return для ExpectedCondition имеет тип Boolean и возвращает значение true, либо возвращает not null для всех других ExpectedCondition типов.

Ожидаемые условия
Существуют некие условия, которые часто встречаются при автоматизации веб-сайтов. Ниже перечислены реализации каждого. Связки в Selenium Python предоставляют некоторые удобные методы, так что вам не придется писать класс expected_condition самостоятельно или же создавать собственный пакет утилит.

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable - it is Displayed and Enabled.
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present
from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) element = wait.until(EC.element_to_be_clickable((By.ID,"someid")))
Модуль expected_conditions уже содержит набор предопределенных условий для работы с WebDriverWait.

5.2 Неявные ожидания

Неявное ожидание указывает WebDriver"у опрашивать DOM определенное количество времени, когда пытается найти элемент или элементы, которые недоступны в тот момент. Значение по умолчанию равно 0. После установки, неявное ожидание устанавливается для жизни экземпляра WebDriver объекта.

From selenium import webdriver driver = webdriver.Firefox() driver.implicitly_wait(10) # seconds driver.get("http://somedomain/url_that_delays_loading") myDynamicElement = driver.find_element_by_id("myDynamicElement")
Перейти к следующей главе.

    Webasyst-приложение «Рассылки» умеет автоматически подсчитывать количество просмотров отправленного вами сообщения получателями. Чтобы использовать эту возможность, достаточно добавить любое изображение в сообщение с помощью встроенного визуального...

    Есть решение

    Чтобы это работало, нужно добавлять изображения через визуальный редактор, при создании рассылки. Подробнее об этом написано в статье Как узнать, сколько получателей прочитали ваше email-сообщение на нашем сайте.

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

    В приложении «Рассылки» есть встроенная функция, которая позволяет определять, сколько получателей email-рассылки прочитали ваше сообщение.Загрузка изображенийДля сбора такой информации используются изображения, вставленные в текст сообщения. Каждый...

    Введение Данное сообщение - не вопрос. Это, скорее, статья об одном из способов выжать из своего интернет-магазина больше производительности. Целевая аудитория статьи - пользователи с навыками администрирования и владельцы магазинов, у которых в...

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

    +2

    Здравствуйте. В приложении "Рассылки" не увидел возможности скачать список emailов клиентов, которые прочитали письмо. Есть возможность выгружать по 50 штук, но при нескольких сотнях прочитанных сообщений это займет много времени.Для чего это...

    Есть решение

    Ерунда такая с моей рассылкой. Если отправляю тестовую рассылку и пишу там пару выдуманых почтовых ящиков то в отчете рассылки показаны отказы. А когда отправляю своим подписчикам 2500 то нет ни одного отказа зато 70% неизвестно. Прочитано:663(30%)...

    Ваще-то, я должен сказать, 30% прочитано и 70% неизвестно - это весьма и весьма достойный результат. База адресов вполне живая. Я почти готов поверить, что там может и не быть битых ящиков.Когда будете отправлять следующую живую, не тестовую рассылку, добавьте туда вперемешку пару битых адресов (но чтобы не полная абракадабра, а которые выглядят как хорошие) и пару своих хороших адресов. Другого способа убедиться в том, что всё хорошо, я не могу придумать.Давайте ещё я опишу жизненный путь Email письма....

    -8 Не принято

    Предлагаю, чтобы плагины и приложения разработчиков перед продажей шифровались (обфусцировались) после проверки WebAsyst. Чтобы нельзя было втупую купить плагин и завтра продавать "копию" за полцены от оригинала.

    +1 Просто крик души в оформление заказа

    Являемся разработчиками темы https://www.webasyst.ru/store/theme/juicer_land/ в которой мы переделали оформление заказа, в частности доставка была представлена в виде вкладок аккордиона, за основы была взята тема для разработчиков Dummy. В итоге...

    Здравствуйте. Есть необходимость скачивания списка "неизвестно" после проведения рассылки для повторной рассылки по этой базе. В данный момент приложение "рассылки" позволяет выгружать вручную по 50 email адресов. При базе в 50...

    Если у вас большой список получателей (несколько тысяч), то отправка сообщения может занять продолжительное время, превышающее установленный на сервере лимит для выполнения скриптов. Для того чтобы ваши рассылки всегда завершались несмотря на серверные...

    Прием платежей в Shop-Script через платежную систему «Яндекс.Деньги» осуществляется с помощью плагина, который нужно установить с помощью приложения «Инсталлер». Полезно знать: подключаясь к платежной системе через Webasyst, вы...

    Добавить вкладку в админке

    Подскажите, если добавить новую вкладку, в админке ShopScript5, возле товаров и отчетов, (вкладка называется Акции) то как можно перенести товари во вкладку акции, где можно прочитать об этом!

    подскажите как сгенерировать sitemap.xml для конкретного сайта на webasyst прочитал что framework делает это автоматически, но у меня есть сайт, а файла sitemap нет что конкретно нужно сделать, чтобы он появился

    После того как рассылка прошла, есть куча отказов адресов. где-то процентов 10.. перебирать руками это мучение. Сделайте ссылку из адреса который в отказе на аккаунт, или в поиск, куда угодно лишь бы не копировать эти адреса и не искать через поиск.....

    Добрый день. В изначальном варианте темы при добавлении товара в корзину обновление корзины происходило без перезагрузки страницы, товар просто улетал в корзину и сразу можно було видеть новую сумму заказа. После незначительных улучшений в теме...

    Безопастность хеша паролей

    Добрый день, прочитал статью: http://www.webasyst.ru/blog/security-improvement-advices-1/ Хочу сделать кастомный алгоритм шифрования и добавить соль. В предлагаемой функции в статье кажется не правильно указана соль. По идее солью должно быть...

    Есть решение

    В логике работы плагина необходимо читать (писать) данные из основных таблиц приложения shop-script 5. Если я пишу и использую плагин только для себя, я могу просто напрямую из своей модели прочитать и записать данные в эти таблицы (тут все...

    Теоретически со временем поменяться может что угодно — всего заранее не предусмотришь. Например, вёрстка бекенда, к которой привязан плагин. Тут уж ничего не поделаешь — придётся адаптировать. Так же и в вашем случае. Если речь идёт о записи важной информации в базу данных, имеет смысл для таких действий использовать механизм исключений, чтобы с их помощью сигнализировать пользователю, если что-то пойдёт не так.

    Здравствуйте!У меня есть домен Lovere.ru. Сейчас он лежит на хостинге у другого провайдера.Создан сайт на вашей платформе http://lovere.host.webasyst.com/Меня все устроило и теперь я хочу открепить свой домен он своего хостера и положить свой домен к...

У всех, кто впервые сталкивается с MODx Evolution, очень часто возникают подобные вопросы: как вывести дату создания документа, как вывести автора документа, как вывести заголовок родителя и т.д. и т.п. Согласитесь, не для всех это может оказаться элементарным на первых шагах изучения MODx. А сколько было потрачено нервов и времени в бесплодных попытках найти нужный ответ! И не всегда даже найденный ответ бывает очевиден и понятен. Хватит! Настало время дать сразу все ответы на все вопросы. Ну в меру собственных сил и знаний, конечно. В этой статье я буду собирать все подобные вопросы и стараться дать максимально подробный ответ, конечно же все это будет сопровождаться готовыми рабочими примерами, которые каждый сможет применить на практике.

Автор не считает себя великим гуру, поэтому, в подготовке этой статьи рассчитывает на помощь читателей. Присылайте свои вопросы и найденные решения, комментируйте и предлагайте альтернативные подходы, критикуйте и становитесь соавторами этой подборки.

Спрашивали? Отвечаем!

Сниппеты можно вызывать двумя способами:

[[НазваниеСниппета]] - кэшируемый вызов сниппета
[!НазваниеСниппета!] - некэшируемый вызов сниппета

Если вызов основного сниппета некэшируемый, то в шаблоне сниппет обязательно должен быть кэшируемым. Более того, чтобы избежать проблем с выводом сниппетов (например с постраничным разбиением), основной сниппет желательно всегда делать некэшируемым.

К примеру, имеем некэшируемый вызов Ditto :

[!Ditto? &startID=`10` &tpl=`ditto_tpl`!]

у которого в шаблоне ditto_tpl надо вызвать сниппет Wayfinder :


[`]]

Расширим задачу, сформулировав вопрос так: Как вообще выводить даты? Т.е. это может быть любая дата, как в шаблоне для Ditto так и на странице самого документа.

а) Задача: вывести дату создания документа в ленте новостей

Для вывода даты нам потребуется вставить в шаблон news_tpl в месте вывода этой даты специальный плэйсхолдер Ditto - [+date+] - он выводит дату в установленном формате (которую мы зададим позже, при вызове Ditto). По умолчанию используется значение createdon (дата создания документа). Может принимать значения: editedon (дата последнего редактирования) и pub_date (дата публикации документа).

Создаем чанк news_tpl :


[+date+] | [+longtitle+]

[+introtext+]


Для того, чтобы задать параметр из которого Ditto будет брать значение даты используем &dateSource=`editedon` . Для того чтобы задать формат даты, используем &dateFormat=`%d.%m.%Y` , где значением выступает любой валидный формат времени, который соответствует правилам функции PHP - strftime:

Символы для обозначения формата времени функции strftime
%a сокращенное название дня недели в текущей локали Пн
%A полное название дня недели в текущей локали 18.03.2019
%b сокращенное название месяца недели в текущей локали 18.03.2019
%B полное название месяца недели в текущей локали Март
%c предпочтительный формат даты и времени в текущей локали 18.03.2019
%C столетие (год, деленный на 100 и огругленный до целого, от 00 до 99) 18.03.2019
%d день месяца в виде десятичного числа (от 01 до 31) 18.03.2019
%D аналогично %m/%d/%y 18.03.2019
%e день месяца в виде десятичного числа, если это одна цифра, то перед ней добавляется пробел (от " 1" до "31") 18.03.2019
%g подобно %G, но без столетия. 18.03.2019
%G Год, 4-значное число, соответствующее номеру недели по ISO (см. %V). Аналогично %Y, за исключением того, что если номер недели по ISO соответствует предыдущему или следующему году, используется соответствующий год. 2019
%h аналогично %b мар
%H номер часа от 00 до 23 18.03.2019
%I номер часа от 01 до 12 18.03.2019
%j номер дня в году (от 001 до 366) 077
%m номер месяца (от 01 до 12) 18.03.2019
%M минуты 18.03.2019
%n символ "\n" 18.03.2019
%p `am" или `pm", или соответствующие строки в текущей локали 18.03.2019
%r время в формате a.m. или p.m. 18.03.2019
%R время в 24-часовом формате 18.03.2019
%S секунды 18.03.2019
%t символ табуляции ("\t") 18.03.2019
%T текущее время, аналогично %H:%M:%S 18.03.2019
%u номер дня недели от 1 до 7, где 1 соответствует понедельнику 18.03.2019
%U порядковый номер недели в текущем году. Первым днем первой недели в году считается первое воскресенье года. 18.03.2019
%V Порядковый номер недели в году по стандарту ISO 8601:1988 от 01 до 53, где 1 соответствует первой неделе в году, в которой как минимум 4 дня принадлежат этому году. Первым днем недели считается понедельник. (Используйте %G or %g для определения соответствующего года) 12
%W порядковый номер недели в текущем году. Первым днем первой недели в году считается первый понедельник года. 18.03.2019
%w номер дня недели, 0 соответствует воскресенью 18.03.2019
%x предпочтительный формат даты без времени в текущей локали 18.03.2019
%X предпочтительный формат времени без даты в текущей локали 18.03.2019
%y год без столетия (от 00 до 99) 18.03.2019
%Y год, включая столетие 18.03.2019
%Z временная зона в виде смещения, аббривеатуры или полного наименования 18.03.2019
%% символ `%" 18.03.2019

Примеры вызова Ditto с разными значениями даты:

[!Ditto? &startID=`10` &tpl=`news_tpl` &dateSource=`createdon` &dateFormat=`%d.%m.%Y` &display=`5`!] // дата создания документа
[!Ditto? &startID=`10` &tpl=`news_tpl` &dateSource=`editedon` &dateFormat=`%d.%m.%Y` &display=`5`!] // дата последного редактирования документа
[!Ditto? &startID=`10` &tpl=`news_tpl` &dateSource=`pub_date` &dateFormat=`%d.%m.%Y` &display=`5`!] // дата публикации документа

б) Задача: вывести дату на странице самой новости

Казалось бы, чего проще? Меняем в чанке news_tpl все + на * и нужный чанк готов. Но в MODx нет специального тега [*date*], поэтому, вместо него придется использовать [*createdon*] , [*editedon*] или [*pub_date*] .

Для начала пробуем [*createdon*] или [*editedon*] и получаем вместо даты что-то типа этого:

1318407717

Почему же так получилось? Потому что, любое время в БД записывается в виде Unix Timestamp — количество секунд от 1 января 1970 года до текущего момента. Зачем же такое придумали? Ну, например, для того, чтобы была возможность оперировать датой в независимости от ее формата. На самом деле, это будет легко перевести в нужный нам формат, но об этом чуть позже.

Берем следующий параметр [*pub_date*] и получаем:

Ну а тут то что не так, спросите вы? Оказывается, по-умолчанию параметр pub_date не устанавливается, поэтому его значение равно 0 и попытка его вывести выдаст 0 или 01.01.1970. Чтобы избежать этого, придется параметр pub_date делать обязательным к заполнению или установить ему значение по умолчанию. В этом нам поможет ManagerManager . Но даже если параметр будет заполнен, на выходе мы вновь увидим количество секунд, прошедших с 01.01.1970.

Решение 1. На помощь придет PHx , только прежде чем его устанавливать, ознакомьтесь с возможными проблемами. При установленном PHx вывод даты можно сделать таким образом:

[*createdon:date=`%d.%m.%Y`*] // дата создания документа
[*editedon:date=`%d.%m.%Y`*] // дата последнего редактирования документа
[*pub_date:date=`%d.%m.%Y`*] // дата публикации документа

Кстати, PHx можно использовать и в шаблоне Ditto, вставив вместо плэйсхолдера [+date+] один из этих плэйсхолдеров:

[+createdon:date=`%d.%m.%Y`+] // дата создания документа
[+editedon:date=`%d.%m.%Y`+] // дата последнего редактирования документа
[+pub_date:date=`%d.%m.%Y`+] // дата публикации документа

В этом слачае, при вызове Ditto параметры &dateSource и &dateFormat не нужны.

Решение 2. Для вывода даты в нужном формате можно воспользоваться сниппетом. Создаем новый сниппет, назовем его, к примеру, DateFormat , и помещаем в него следующий код:

setlocale(LC_ALL, "ru_RU.UTF-8");

if ($val == "") $val=time();
if ($format == "") $format = "%d.%m.%Y";
return strftime($format, $val);
?>

В том месте, где нам необходимо вывести дату, помещаем такой вызов этого сниппета:

[!DateFormat? &val=`[*createdon*]` &format=`%d.%m.%Y`!]

В параметре &val мы задаем значение для даты, а в параметре &format нужный формат. Сниппет может применяться как на странице новости, так и в шаблоне для Dito, не забудьте, если Ditto вызывается некэшируемым, то в его шаблоне сниппет должен вызываться как кэшируемый.

Если вызвать сниппет вообще без параметров:

[!DateFormat!]

то он выведет текущую дату в формате заданном по умолчанию: "%d.%m.%Y", этот формат можно поменять в коде сниппета.

Вот пример, где это может пригодиться: Необходимо отсортировать горящие туры по дате вылета. Дата вылета задается в TV-параметре data_toura с типом ввода Date. При этом, эта дата должна быть выведена в ленте горящих туров и на странице самого тура в формате %d-%m-%Y.

Для того, чтобы можно было отсортировать туры в ленте горящих туров, которая выводится с помощью снипета Ditto, мы будем использовать параметр &orderBy=`data_toura ASC` . Для того, чтобы сортировка у нас получилась правильная, необходимо задать параметру data_toura значение в формате количества секунд, прошедших с 1 января 1970 года. Для этого нам необходимо установить у этого параметра значение Визуальный компонент как Unixtime :

Для отображения этой даты в шаблоне Ditto и на странице тура будем использовать PHx или сниппет DateFormat из предыдущей статьи:

[!DateFormat? &val=`[*data_toura*]` &format=`%d-%m-%Y`!] // на странице тура
[` &format=`%d-%m-%Y`]] // в шаблоне Ditto

Создаём сниппет convertDate и вставляем в него такой код:

$MyDate= (isset($MyDate)) ? $MyDate: $modx -> documentObject["MyDate"];
$type= (isset($type)) ? $type: $modx -> documentObject["type"];
$monthes = array("","января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря");
$day = date("j" ,$MyDate);
$month = $monthes;
$year = date("Y",$MyDate);

echo $day." ".$month." ".$year." года";
?>

Вызываем сниппет:

[!convertDate? &MyDate=`[*createdon*]`!] // в документе MODx
[`]] // в шаблоне Ditto

В качестве значения параметра &MyDate могут выступать createdon, editedon, pub_date, unpub_date и TV-параметр с типом ввода Date и визуальным компонентом Unixtime .

Решение 1. Для этой цели можно воспользоваться возможностями PHx , если он у вас установлен.

Логин того, кто создал документ, будет выводиться таким образом:

[*createdby:userinfo=`username`*] // в докуенте MODx
[+createdby:userinfo=`username`+] // в шаблоне Ditto

К примеру, если документ создал администратор, то выведется его логин admin.

Но для того, чтобы вывести не логин, а полное имя пользователя, которое указано в соответствующем поле в настройках пользователей, делаем так:

[*createdby:userinfo=`fullname`*]

В этом случае выведется Default admin account - это значение полного имени администратора по умолчанию. Чтобы вывелось Администратор или как-то иначе, необходимо заменить его полное имя в Пользователи >> Управление менеджерами .

Для вывода E-mail пользователя:

[*createdby:userinfo=`email`*]

Решение 2. Плагин PHx можно заменить простым сниппетом. Создадим новый сниппет UserInfo с таким кодом:

/*
* UserInfo - сниппет для получения данных пользователя
*/
$output = "";
$userinfo = $modx->getUserInfo($id); //Используем Id пользователя для получения массива с данными пользователя
$out1 = $userinfo["fullname"]; // Получаем полное имя пользователя
$out2 = $userinfo["phone"]; // Получаем телефон пользователя
$out3 = $userinfo["email"]; // Получаем электронный адрес пользователя
$output = "Автор: " . $out1 . " Телефон: " . $out2 . " E-mail: " . $out3;
return $output;
?>

В шаблоне помещаем такой вызов сниппета:

[!UserInfo? &id=`[*createdby*]`!]

&id=`[*createdby*]` - передаем в наш сниппет ID пользователя, создавшего документ. С помощью параметра editedby можно передать ID пользователя, редактировавшего документ.

С помощью этого сниппета можно выводить любые данные пользователя из таблицы user_attributes. Описание getUserInfo .

Решение 1. Вновь расширим задачу: Как вообще выводить параметры родительского документа?

Вывести ID родительского документа мы можем с помощью специального тега MODx:

[*parent*] // в документе MODx
[+parent+] // а шаблоне Ditto

Чтобы вывести заголовок родительского документа, применяем PHx:

[*id:parent=`pagetitle`*] // в документе MODx
[+id:parent=`pagetitle`+] // а шаблоне Ditto

Чтобы вывести ID родителя родителя, т.е. дедушки:

[*parent:parent=`id`*] // в документе MODx
[+parent:parent=`id`+] // а шаблоне Ditto

Чтобы вывести заголовок прадедушки:

[*parent:parent=`id`:parent=`pagetitle`*] // в документе MODx
[+parent:parent=`id`:parent=`pagetitle`+] // а шаблоне Ditto

[*parent:parent=`id`:parent=`id`:parent=`pagetitle`*] // в документе MODx
[+parent:parent=`id`:parent=`id`:parent=`pagetitle`+] // а шаблоне Ditto

Таким образом можно получать не только id или заголовок, а и любой другой параметр, например расширенный заголовок родителя:

[*id:parent=`longtitle`*] // в документе MODx
[+id:parent=`longtitle`+] // а шаблоне Ditto

Решение 2. Если по каким-то причинам вы не можете использовать PHx, на помощь придет сниппет getField .

Скачайте и установите сниппет, как это описано в документации . Чтобы получить заголовок родительского документа, сниппет надо вызвать с такими параметрами:

[!GetField? &parent=`0` &field=`pagetitle`!]

При parent=0 будет использоваться родительский документ. В качестве значения параметра field можно указывать любое поле документа или TV-параметр.

Чтобы получить заголовок или другое поле "дедушки", используются следующие параметры:

[!GetField? &parent=`1` &parentLevel=`2` &field=`pagetitle`!]

При parent=1 используется корневой каталог. Параметр parentLevel определяет глубину вложенности от текущего документа до корневого каталога: 0 - корневой документ, 1 - родитель, 2 - дедушка, 3 - прадедушка и т.д.

Этот сниппет позволяет выводить любой параметр любого документа. С помощью параметра docid можно задать ID интересующего документа, а с помощью параметра field необходимое поле или TV-параметр:

[!GetField? &docid=`25` &field=`pagetitle`!]

К примеру, выводим ленту новостей и необходимо, чтобы длина заголовка не превышала 200 символов, при этом, надо чтобы слова не обрезались и в конце строки появлялись три точки.

Для этих целей используем сниппет truncate . Код сниппета:

$lenf = $len;

//Заменяет символы перевода строки на HTML тег
$order = array("\r\n", "\n", "\r");
$replace = "
";
$what = str_replace($order, $replace, $text);

if (strlen($what) > $lenf) {
$what = preg_replace("/^(.{" . $lenf . ",}?).*$/is", "$1", $what) . "...";
}
return $what;
?>

Вызов сниппета будет таким:

[!truncate? &text=[*pagetitle*] &len=200!] // в документе MODx
[ &len=200]] // а шаблоне Ditto

Сниппет обрежет текст до определенного количества символов не обрезая слова и добавит в конце три точки.

Придется отредактировать файл assets/snippets/ditto/formats/rss.format.inc.php
На 80 строчке есть такая запись:

$GLOBALS["dateSource"] = isset($dateSource) ? $dateSource: "createdon";
// date type to display (values can be createdon, pub_date, editedon)

// set tpl rss placeholders
$placeholders["rss_date"] = array($GLOBALS["dateSource"],"rss_date");

Т.е. по умолчанию используется createdon . Надо заменить на pub_date , т.е.:

$GLOBALS["dateSource"] = isset($dateSource) ? $dateSource: "pub_date";

Если у вас псевдонимы генерируются автоматически с помощью плагина TransAlias, то такие знаки препинания, как "!", ":", "," и т.д. автоматически появятся и в вашем псевдониме, если они присутствуют в заголовке. Но если запятая или восклицательный знак не влияют на работоспособность псевдонима, тем не менее, их присутствие вряд ли его украсит. К счастью, это легко исправляется.

В настройках TransAlias достаточно выбрать в параметре Restrict alias to значение lowercase alphanumeric и псевдонимы будут генерироваться правильно, т.е. без лишних знаков препинания.

Для этого воспользуемся сниппетом ChildCounter (сниппет найден ):

$docid = isset($docid) ? intval($docid) : $modx->documentIdentifier;
$depth = isset($depth) ? intval($depth) : 0;
$isfolder = isset($isfolder) ? intval($isfolder): 0;
$tpl = isset($tpl) ? $tpl: -1;
$published = isset($published) ? intval($published): 1;
$davailable = $modx->getChildIds($docid, $depth);

if ($davailable){
$where = ($tpl > 0) ? "template=".$tpl: "isfolder=".$isfolder;
$dcount = $modx->getDocuments($davailable, $published, 0, "id", $where);
return count($dcount);
}
return 0;
?>

Параметры сниппета ChildCounter :

&dicId - ID сканируемой папки

&depth - глубина сканирования
&isfolder - Если 1 - вернёт количество папок, если 0 - количество документов НЕ папок. Значения 0 или 1. По умолчанию 0.
&published - Если 0 - вернёт количество неопубликованных документов, если 1 - количество опубликованных документов. Значения 0 или 1. По умолчанию 1.

&tpl - если указан, то возвращает количество документов с шаблоном id которого равен &tpl

Пример использования:

[]

AlterTitle выводит pagetitle если не заполнен longtitle .

Пример использования:

[`]]

&id=`[*id*]` - передаем в сниппет ID ресурса, в котором вызываем сниппет. Фактически, этот сниппет можно использовать и для вывода заголовка родителя, передав [*parent*].

Пример 2.

Аналогичную конструкцию можем применять и для других параметров. Например, у нас есть параметр foto у шаблона Новости , в котором содержится картинка для новостей, но не у всех новостей будут картинки. И если у новости нет картинки, нам необходимо вывести картинку "заглушку". Я поступил следующим образом, создал еще один параметр nofoto тип ввода Image и назначил его шаблону Новости . В значении по умолчанию указал путь к картинке "заглушке". С помощью плагина ManagerManager спрятал этот параметр от всех пользователей:

Mm_hideFields("nofoto"); где

&directOutput=`1` - выводим результат работы сниппета напрямую, без использования плэйсхолдеров

После встречи очередного нового года и длинных и запойных выходных, вы возвращаетесь к работе и обнаруживаете, что у всех ваших сайтов надо обновлять год в копирайте. А почему бы не автоматизировать сей процесс? Подобная идея уже приходила в голову не только мне, поэтому я довольно быстро нашел готовое решение в пару строчек и немного его модифицировал.

Создайте новый сниппет (название не так важно, ну пусть будет copyright ) с таким кодом:

$copyfrom = (isset($copyfrom)) ? $copyfrom: "2012";
$copyholder = (isset($copyholder)) ? $copyholder: " [(site_name)]";
if (date("Y") != $copyfrom) { $copytill = " - " . date("Y"); }
$copyright = " " . $copyfrom . $copytill . $copyholder;
return $copyright;
?>

Пример вызова:

[]

[]

©holder=`Иван Иванович Иванов.` - обладатель авторских прав. По умолчанию - название сайта.

  • Блог компании Фонд развития интернет-инициатив
    • Перевод

    User story является неотъемлемой частью гибких методик разработки. С помощью нее можно разбить свою работу на серию задач, выполнение каждой из которых привносит ощутимую ценность в процесс реализации проекта. Все эти задачи можно обсуждать и распределять по важности независимо друг от друга.

    User story или «пользовательские истории/пожелания пользователя» являются методикой, опирающейся на другие практики agile, включая принципы непрерывной поставки и непосредственного общения с пользователями. Недостаточно просто понять, каким будет ваш пользователь; реальный пользователь вашей системы должен находиться рядом с командой на протяжении длительного времени.


    User story вкратце описывает:

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

    Обсудите историю перед планированием этапов с:

    • соответствующими членами команды;
    • экспертами по данному вопросу;
    • заинтересованными сторонами.

    Карточки пожеланий пользователя

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

    При оформлении карточки с пожеланием пользователя убедитесь, что оно (пожелание) хорошо сформулировано. Не пропускайте часть, в которой объясняется необходимость создания данного элемента системы только потому, что это может вызвать у вас трудности.

    Вы можете выработать список критериев приемки при выполнении пользовательских требований к системе. Он должен стать напоминанием к тому, чтобы протестировать или проверить определенные вещи, которые могут быть затронуты при обсуждении. Но не нужно ориентироваться на него при определении объемов работ на основании user story.

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

    Структура

    Карточка составляется по стандартной схеме:
    • заголовок;
    • заказчик (actor, актер);
    • примечание;
    • цель.
    Она не отражает каждую мелочь, но вы должны детально обсудить пользовательскую историю в подходящее для команды время.

    Движение «от цели»
    Создание полезных программных систем трудоёмко, поэтому вы должны быть уверены, что решаете правильную проблему. В гибких методологиях применяют подход «от обратного» – что пытается получить пользователь системы на выходе? Если вы углубитесь в решение данного вопроса без достаточного понимания ваших пользователей, вы рискуете решить не ту проблему или найти решение, которое на самом деле не подходит вашим пользователям в реальной жизни. Поэтому самой важной частью user story является её цель.
    Цель
    Понимание целей поможет вам при решении вопроса о том, выполнили ли вы требования пользователя, иными словами, соответствует ли проделанная вами работа цели, стоящей перед пользователем?

    При написании user story с вашей командой разработчиков всегда начинайте с обдумывания и обсуждения целей вашего пользователя:

    • почему он хочет использовать эту систему?
    • чего он пытается достичь?
    • что заставило его искать ваш сервис?
    • в каких условиях он использует её: дома/на работе/по телефону/во время ухода за ребенком?
    • как часто он пользуется ей?
    Сюзанна и Джеймс Робертсон дают отличный совет по этому поводу в третьем издании книги «Mastering the Requirements Process».
    Заказчик (актер)
    Подробное описание заказчика (актера) поможет вам разбить процесс построения взаимодействия на логически связанные отрезки.
    Иногда заказчик будет пользователем вашей системы, в других случаях – администратором, техником или менеджером вашей организации.
    Убедитесь, что вы достаточно хорошо знаете своих пользователей на основании уже проделанной работы или проведенных ранее исследований. Если это не так, найдите время расширить свое понимание пользователей.
    Примечания
    Используйте их как блок передачи информации об основом типе взаимодействия, которое должно рассматриваться как часть пользовательского опыта. Помните, что карточка не должна отражать каждую деталь этого взаимодействия.
    Общение с пользователем напрямую
    Agile-команды предпочитают общение лицом к лицу подробной документации.

    Разговор лицом к лицу:

    • быстрее;
    • точнее, чем написанная документация;
    • позволяет разработчикам выстроить детальную модель целей пользователя, рабочих процессов, ограничений и многих вопросов, которые должны быть приняты во внимание при разработке ПО.
    Карточка – это всего лишь шаблон, гарантия, что разговор состоится в подходящее время. Воспользуйтесь карточками и несколькими вариантами для завязки разговора, чтобы оценить, сколько времени вам понадобится, чтобы завершить работу над составлением user story и поместить её в бэклог проекта.

    Когда начнется непосредственно разработка, пообщайтесь с пользователями или их представителями на предмет уточнения деталей требований. User story – это обобщение всех разговоров, набросков и диаграмм – не просто карточка. Необязательно записывать и хранить все разговоры, но нужно правильно интерпретировать их в автоматизированные тесты или работающий код.

    Использование пользовательских историй на этом этапе позволит избежать «информационной перегрузки» – болезненного состояния, при котором пытаешься угадать детали какой-либо цели из далекого будущего.

    Критерии приемки для user story

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

    Истории «работают» только для agile-команд

    Успех user story зависит от регулярного общения напрямую между разработчиками и пользователями или его представителями. Ваш сервис-менеджер и другие представители пользователя должны быть доступны для общения с разработчикам каждый день, и иметь достаточно времени, чтобы обдумать ваши вопросы и ответить на них. Не стоит недооценивать то, как много времени может отнимать эта работа!

    Откуда берутся пожелания пользователя

    Пожелания могут поступать из многих мест, но наиболее распространённые источники включают в себя:
    1. Воркшопы по написанию user story (чаще всего происходят на начальном этапе проекта); команда разработчиков и заинтересованные стороны собираются, чтобы обозначить пожелания пользователей.
    2. Интервью с реальными пользователями – в идеале, вы должны найти несколько пользователей, к которым команда разработчиков будет иметь постоянный доступ.
    3. Представители пользователя в составе вашей команды – это может быть сервис-менеджер или владелец продукта.
    4. Наблюдение – посмотрите, как реальные пользователи работают в вашей системе.
    Мы обсудили данный материал с рядом стартапов 4-го набора Акселератора ФРИИ [ближайший День открытых дверей Акселератора состоится в следующий четверг 12-го февраля 2015 г. ]:

    1. Используете ли вы user story? К каким источникам вы обращаетесь при составлении user story?

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


    Cкорее нет, чем да. Мы сначала разработали продукт и уже потом общались с пользователями. Да, мы вносили определённые изменения в контент, основываясь на отзывах, но никогда не ставили себе целью полностью основывать «таски» на user story.


    Термин user story мы никогда не использовали, но по факту отвечали на вопросы «Кто наш клиент?», «Как он использует наш сервис?» и «Почему ему нужен наш сервис?». Информацию получали из заявок клиентов, которые поступают к нам на сайт, и из личного общения с теми клиентами, кто заказал и кто не заказал наши услуги (услуги переводчиков).


    Александр Грицай, CEO, Forecast NOW!

    Мы используем методику выявления потребностей клиентов с помощью обратной связи, опросов, живого общения (у нас непростой B2B-продукт, и число клиентов измеряется десятками), ранжируем их, выделяем на спринт приоритетные по критериям полезности для текущих и будущих пользователей, раз в месяц выпускаем обновление.


    Ольга Книга, со-основатель и CEO, Merku.ru

    Мы используем их, как и любой инструмент - тогда, когда это необходимо. В первую очередь, при проектировании посадочной страницы и новых функций продукта.


    Согласны ли вы с утверждением, что методика user story подходит только для agile-команд?

    Александр Богомолов, экс-проектный менеджер, ПоискСтроек

    Не очень понял вопроса – чем мешает создание историй любой команде? Да – в проектах с жестким ТЗ истории не очень нужны на первый взгляд, но они помогают превратить такую разработку в более осмысленную. Помогают понять, зачем вообще реализуется та или иная функция.


    Леонид Тощев, технический директор, Datamonkey

    Сейчас понятие agile настолько размылось– мне кажется, нет таких компаний, которые не agile. Соответственно, user story могут использоваться всеми.


    Мария Теркина, со-основатель и CEO, Глоберлэнд

    Думаю, user story вписывается в agile-подход, но может использоваться и в других подходах к разработке.


    Мария Подоляк, со-основатель Gagarin Labs

    Подход к проектированию функционала продукта или интерфейса при помощи user stories – это же изначально не разработка Agile. Так называемые use cases (юс кейсы, примеры использования) существуют давно в практике разработки технических заданий. Подход такой же, немного был видоизменен под нужды Agile.

    В частных разговорах двух менеджеров продуктов можно услышать: «О, мы начали делать новый продукт для распознавания цвета кошек на фотографиях!» В ответ можно услышать: «Давай юс кейс». Мы разговариваем пользовательскими историями, ведь только так можно понять, какая «боль пользователя» решается, какой инновационный или кардинально иной способ решения проблемы используется, как это происходит на уровне функционала. История помогает «примерить» решение на себя.

    User stories используются и при проектировании интерфейсов. С этим подходом я познакомилась в Школе проектирования интерфейсов. Мы рисовали портрет пользователя (еще их иногда персонами называют), писали сценарий истории, дальше мы вырезали из бумаги интерфейс для того, чтобы разыграть сценарий или историю (так происходило тестирование гипотезы).

    В ноябре я попробовала применить такой же подход для проектирования контент-стратегии компании или продукта на семинаре в Школе интернет-маркетинга в Нижнем Новгороде. И прекрасно получилось. Так что подход применим ко всему в продукте: функционалу, интерфейсу, маркетингу и т.д. Заставляет думать «головой пользователя», понимать его проблему, а не опускаться в галлюцинации.
    Так что Agile ничего нового не сделал, просто адаптировал существующий подход под свои нужды. Добавить метки



    
    Top