пятница, 25 мая 2007 г. - www.msmirnov.ru

Использование представлений для сокрытия промежуточных данных в Microsoft SQL Server

Здесь я привожу копию моей статьи "Использование представлений для сокрытия промежуточных данных в Microsoft SQL Server", опубликованной в рассылке MS SQL Server - дело тонкое #359 от 25.05.2007 г (http://www.sql.ru/subscribe/2007/359.shtml#20)

Использование представлений для сокрытия промежуточных данных в Microsoft SQL Server.

Введение
При разработке различного рода коммерческих баз данных довольно часто встает задача обновления заранее просчитанных итоговых данных. Чаще всего это требуется для предоставления различного рода отчетности. Не всегда для этого используется технология OLAP, и поэтому в этой статье я приведу описание приема, который позволяет организовать незаметное для пользователя обновление таких данных.
Для начала сформулируем проблему, которую должен решить данный механизм. Проще всего сделать это на примере. Предположим, что используя базу Northwind, нам требуется предоставлять отчетность о кол-ве и объемах продаж за любой день по любому товару. Мы можем сделать это, создав View следующего вида:

CREATE VIEW [Sales by Product]
AS
SELECT     Orders.OrderDate, [Order Details].ProductID, 
      SUM([Order Details].UnitPrice * [Order Details].Quantity) AS Amount, 
      SUM([Order Details].Quantity) AS Quantity
FROM         [Order Details] INNER JOIN
                      Orders ON [Order Details].OrderID = Orders.OrderID
GROUP BY Orders.OrderDate, [Order Details].ProductID
Однако, если в исходных таблицах Orders или Order Details будет находится больше количество записей (сотни миллионов), то выборка из такого view будет очень медленной. Быстро получить данные из него будет невозможно.
Возможным решением проблемы могла бы быть замена view на таблицу с периодически обновляемыми данными:

CREATE TABLE [Sales by Product] (
 OrderDate datetime NOT NULL ,
 ProductID int NOT NULL ,
 Amount money NOT NULL ,
 Quantity int NOT NULL 
)
Имея возможность время от времени обновлять такую таблицу на основании данных из таблиц заказов мы могли бы предоставить пользователям отчетную информацию в удобном для них виде. На такой таблице можно было бы создать индексы, которые могут позволить пользователям очень б ыстро осуществлять запрос данных.
Например:

CREATE CLUSTERED
  INDEX [Index1] ON [Sales by Product] (OrderDate, ProductID)
Для того чтобы обновить информацию в такой таблице достаточно было бы просто очистить ее и выполнить запрос на ее заполнение.

DELETE FROM [Sales by Product]

INSERT INTO [Sales by Product] (OrderDate, ProductID, Amount, Quantity)
SELECT     Orders.OrderDate, [Order Details].ProductID, 
          SUM([Order Details].UnitPrice * [Order Details].Quantity) AS Amount, 
        SUM([Order Details].Quantity) AS Quantity
FROM       [Order Details] INNER JOIN
                      Orders ON [Order Details].OrderID = Orders.OrderID
GROUP BY Orders.OrderDate, [Order Details].ProductID
Однако, в связи с тем, что в таблице Orders у нас находится очень много заказов, возникают две проблемы, которые не позволят нам обновлять эту таблицу в рабочее время:
Первая проблема: Очистка и обновление такой таблицы может происходить очень долго. В реальной практике расчет итоговых значений для такой таблицы может быть намного сложнее чем в данном примере. В нем могут участвовать множество таблиц с большим количеством записей в каждой, он может содержать сложные вычисления и т.п, так что он, например, может выполняться несколько часов.
Вторая проблема: В течении всего этого времени пользователи не смогу видеть данные о продажах, так как они были удалены первым запросом.
Описанный здесь механизм позволяет решить обе эти проблемы.

Описание механизма
Для решения первой проблемы нужно принять во внимание, что далеко не все строки таблицы Sales by Product нуждаются в обновлении. Заказы редко вписываются «задним числом», поэтому обычно в обновлении нуждается несколько последних дней и очень редко данные в далеком прошлом. Поэтому, для ускорения процесса расчета можно создать дополнительную вспомогательную таблицу, которая будет содержать даты, для которых требуется пересчет.

CREATE TABLE DatesForUpdates (
 UpdateDate datetime NOT NULL 
)
Такая таблица может заполняться при создании либо обновлении заказов. При наличии такой вспомогательной таблицы процесс обновления Sales by Product может выглядеть так:

DELETE [Sales by Product]
FROM [Sales by Product], DatesForUpdates
WHERE [Sales by Product].OrderDate = DatesForUpdates.UpdateDate


INSERT INTO [Sales by Product] (OrderDate, ProductID, Amount, Quantity)
SELECT     Orders.OrderDate, [Order Details].ProductID, 
          SUM([Order Details].UnitPrice * [Order Details].Quantity) AS Amount, 
        SUM([Order Details].Quantity) AS Quantity
FROM       [Order Details] INNER JOIN
                      Orders ON [Order Details].OrderID = Orders.OrderID
   INNER JOIN DatesForUpdates on Orders.OrderDate = DatesForUpdates.UpdateDate
GROUP BY Orders.OrderDate, [Order Details].ProductID

DELETE FROM DatesForUpdates
Таким образом мы накладываем фильтр на обновляемые данные, существенным образом облегчая запрос.
Однако, это не решает второй проблемы – для конечного пользователя данные по прежнему пропадают на некоторое время.
Казалось бы, решением проблемы может быть отказ от удаления данных перед их вставкой. Мы могли бы подготовить временную таблицу с вновь рассчитанным набором данным, затем обновить строки, которые уже существуют в Sales by Product, вставить новые строки и удалить лишние (на тот случай если часть заказов была удалена). Однако, это не решит данную проблему, потому что даже в этом случае существует вероятность того, что пользователь получит некорректные данные, если, например, выполнит запрос на выборку между операциями обновления и удаления. Использование транзакции в этом случае тоже не является выходом, так как данные в этом случае будут заблокированы и недоступны для пользователя до момента завершения транзакции.
Для решения этой проблемы потребуется провести некоторые преобразования, которые как раз и отражают суть рассматриваемого механизма.
Во-первых: в таблицу Sales by Product добавим поле Version, которое означает версию строки таблицы и может содержать следующие значения:
    1 – строка, доступная для пользователя 2 – строка, содержащая новые данные. Пользователю не доступна. 3 – строка, содержащая устаревшие данные. Пользователю также не доступна
Во-вторых: создадим view

CREATE VIEW dbo.[Report by Product]
AS
SELECT     *
FROM        [Sales by Product]
WHERE     Version = 1
Именно с этим представлением Report by Product, а не с таблицей Sales by Product будет теперь работать пользователь. Видим, что это представление фильтрует строки со всеми номерами строк кроме 1, скрывая таким образом промежуточные данные.
Процесс обновления, однако, по-прежнему будет работать с таблицей Sales by Product и будет происходить так, как описано ниже.
Первоначально все строки имеют версию равную 1 и доступны для пользователя.
OrderDate ProductID Amount Quantity Version
1996-11-07 00:00:00.000 1 216.0000 15 1
1996-11-14 00:00:00.000 1 172.8000 12 1
1996-12-03 00:00:00.000 1 216.0000 15 1
Соответственно запрос к представлению

SELECT *
FROM [Report by Product]
возвращает следующий результат:
OrderDate ProductID Amount Quantity
1996-11-07 00:00:00.000 1 216.0000 15
1996-11-14 00:00:00.000 1 172.8000 12
1996-12-03 00:00:00.000 1 216.0000 15
После выполнения следующего запроса на вставку в таблицу

INSERT INTO [Sales by Product] (OrderDate, ProductID, Amount, Quantity, Version)
SELECT     Orders.OrderDate, [Order Details].ProductID, 
          SUM([Order Details].UnitPrice * [Order Details].Quantity) AS Amount, 
        SUM([Order Details].Quantity) AS Quantity, 2 as Version
FROM       [Order Details] INNER JOIN
                      Orders ON [Order Details].OrderID = Orders.OrderID
   INNER JOIN DatesForUpdates on Orders.OrderDate = DatesForUpdates.UpdateDate
GROUP BY Orders.OrderDate, [Order Details].ProductID
в таблице Sales by Product будут содержаться следующие данные:
OrderDate ProductID Amount Quantity Version
1996-11-07 00:00:00.000 1 216.0000 15 1
1996-11-14 00:00:00.000 1 172.8000 12 1
1996-12-03 00:00:00.000 1 216.0000 15 1
1996-11-07 00:00:00.000 1 300.0000 20 2
1996-11-14 00:00:00.000 2 172.8000 12 2
1996-12-03 00:00:00.000 2 216.0000 15 2
Видим, что новые записи помечены номером версии 2. Поэтому запрос к представлению по прежнему возвращает старый набор данных, так как новые строки через него не видны.
OrderDate ProductID Amount Quantity
1996-11-07 00:00:00.000 1 216.0000 15
1996-11-14 00:00:00.000 1 172.8000 12
1996-12-03 00:00:00.000 1 216.0000 15
Следующий запрос выполнит обновление номеров версий строк:

UPDATE [Sales by Product]
SET Version = CASE WHEN Version = 2 THEN 1 
     ELSE 3
       END
FROM [Sales by Product], DatesForUpdates
WHERE [Sales by Product].OrderDate = DatesForUpdates.UpdateDate
Это приведет к тому, что номера версий строк обновятся так:
OrderDate ProductID Amount Quantity Version
1996-11-07 00:00:00.000 1 216.0000 15 3
1996-11-14 00:00:00.000 1 172.8000 12 3
1996-12-03 00:00:00.000 1 216.0000 15 3
1996-11-07 00:00:00.000 1 300.0000 20 1
1996-11-14 00:00:00.000 2 172.8000 12 1
1996-12-03 00:00:00.000 2 216.0000 15 1
Теперь старые данные стали недоступны через представление – на их место встали новые. Запрос к представлению возвращает следующий результат:
OrderDate ProductID Amount Quantity
1996-11-07 00:00:00.000 1 300.0000 20
1996-11-14 00:00:00.000 2 172.8000 12
1996-12-03 00:00:00.000 2 216.0000 15
После этого мы можем произвести очистку старых данных

DELETE FROM [Sales by Product] WHERE Version = 3

DELETE FROM DatesForUpdates
После очистки таблица вновь остается в исходном состоянии – все строки имеют номер версии равный 1.
Итак, весь процесс обновления выглядит следующим образом –

-- Вставка новых данных с номером версии строк 2
INSERT INTO [Sales by Product] (OrderDate, ProductID, Amount, Quantity, Version)
SELECT     Orders.OrderDate, [Order Details].ProductID, 
          SUM([Order Details].UnitPrice * [Order Details].Quantity) AS Amount, 
        SUM([Order Details].Quantity) AS Quantity, 2 as Version
FROM       [Order Details] INNER JOIN
                      Orders ON [Order Details].OrderID = Orders.OrderID
   INNER JOIN DatesForUpdates on Orders.OrderDate = DatesForUpdates.UpdateDate
GROUP BY Orders.OrderDate, [Order Details].ProductID

-- Обновление версий строк: 2 –> 1, 1 -> 3
UPDATE [Sales by Product]
SET Version = CASE WHEN Version = 2 THEN 1 
     ELSE 3
       END
FROM [Sales by Product], DatesForUpdates
WHERE [Sales by Product].OrderDate = DatesForUpdates.UpdateDate

-- Удаление старых данных с номером версии 3
DELETE FROM [Sales by Product] WHERE Version = 3

-- Очистка DatesForUpdates
DELETE FROM DatesForUpdates
Видим, что первый запрос вставляет в таблицу данные с версией строк равной 2 (новая строка), благодаря чему эти новые данные пользователю не доступны. Сколько бы ни шел процесс расчета и вставки, пользователь не видит этих строк – он продолжает получать старые строки, которые были созданы в процессе предыдущего обновления и имеют значение поле Version равное 1. Второй запрос изменяет номера версий строк – новые данные становятся на место старых (2 исправляется на 1), а старые помечаются как готовые к удалению (версия 1 исправляется на 3). После выполнения этого запроса у пользователя мгновенно исчезают старые данные, а вместо них появляются новые. Затем строки с устаревшими данными (номер версии 3) удаляются. Благодаря такой схеме у пользователя ни в один момент времени не пропадают данные и не возникает ситуации при которой он видит одновременно и старые и новые данные.

Заключение

Данный простой механизм был использован в ряде промышленных разработок и зарекомендовал себя.

Я с радостью рассмотрю любые комментарии и вопросы по данной тематике.
Мои координаты доступны на сайте www.msmirnov.ru

Михаил Смирнов
Руководитель проектов.
Мой сайт - www.msmirnov.ru

воскресенье, 22 апреля 2007 г. - www.msmirnov.ru

Технологии Push и Pull при работе с linked servers в Microsoft SQL Server

Здесь я привожу копию моей статьи "Технологии Push и Pull при работе с linked servers в Microsoft SQL Server", опубликованной в рассылке MS SQL Server - дело тонкое #354 от 22.04.2007 г (http://www.sql.ru/articles/mssql/2007/051803PushAndPullinMicrosoftSQLServerLinkedServers.shtml).


Технологии Push и Pull при работе с linked servers в Microsoft SQL Server


Для обеспечения взаимодействия нескольких серверов Microsoft SQL Server наиболее часто используется технология linked-серверов. При этом, типичной является задача обмена данными между linked-серверами. В данной статье я проведу краткий сравнительный анализ технологий push и pull для решения задачи передачи новых данных.
Провести такой анализ проще всего на примере. Пусть у нас есть два сервера – сервер-источник (SourceServer) и целевой сервер (DestServer). В качестве примера рассмотрим задачу, когда нам необходимо передать таблицу Customers c сервера-источника на целевой сервер.
Технология Push
Технология Push характеризуется “заталкиванием” данных с исходного сервера на целевой сервер. Т.е. SQL-запрос для передачи таблицы Customers будет выполняться на исходном сервере и выглядеть так:

insert into DestServer.Northwind.dbo.Customers
select *
from Customers
Для того, чтобы понять, как такой запрос будет обработан SQL Server’ом, необходимо воспользоваться SQL Server Profiler.
На исходном сервере запрос выполняется в неизменном виде –

insert into DestServer.Northwind.dbo.Customers
select top *
from Customers
go
Однако, на целевом сервере все совсем не так. На нем выполняются следующие запросы -

set implicit_transactions on 
go

declare @P1 int
set @P1=1
declare @P2 bigint
set @P2=8400823175122854
exec sp_getschemalock @P1 output, @P2 output, 
N'"Northwind"."dbo"."Customers"'
select @P1, @P2
go

declare @P1 int
set @P1=180150000
declare @P2 int
set @P2=2
declare @P3 int
set @P3=4
declare @P4 int
set @P4=-1
exec sp_cursoropen @P1 output, 
N'select * from "Northwind"."dbo"."Customers"', 
@P2 output, @P3 output, @P4 output
select @P1, @P2, @P3, @P4
go

exec sp_cursor 180150000, 4, 0, 
N'Northwind.dbo.Customers', 
@CustomerID = N'ALFKI', 
@CompanyName = N'Alfreds Futterkiste', 
@ContactName = N'Maria Anders',
@ContactTitle = N'Sales Representative', 
@Address = N'Obere Str. 57', 
@City = N'Berlin', 
@Region = NULL, 
@PostalCode = N'12209', 
@Country = N'Germany', 
@Phone = N'030-0074321', 
@Fax = N'030-0076545'
go

exec sp_cursor 180150000, 4, 0, 
N'Northwind.dbo.Customers', 
@CustomerID = N'ANATR', 
@CompanyName = N'Ana Trujillo Emparedados y helados', 
@ContactName = N'Ana Trujillo', 
@ContactTitle = N'Owner', 
@Address = N'Avda. de la Constitucion 2222', 
@City = N'Mexico D.F.', 
@Region = NULL, 
@PostalCode = N'05021', 
@Country = N'Mexico', 
@Phone = N'(5) 555-4729', 
@Fax = N'(5) 555-3745'
go

exec sp_cursor 180150000, 4, 0, 
N'Northwind.dbo.Customers', 
@CustomerID = N'ANTON', 
@CompanyName = N'Antonio Moreno Taqueria', 
@ContactName = N'Antonio Moreno', 
@ContactTitle = N'Owner',
@Address = N'Mataderos  2312', 
@City = N'Mexico D.F.', 
@Region = NULL, 
@PostalCode = N'05023', 
@Country = N'Mexico', 
@Phone = N'(5) 555-3932', 
@Fax = NULL
go

exec sp_cursorclose 180150000
go

IF @@TRANCOUNT > 0 COMMIT TRAN
Go

set implicit_transactions off 
go
Видно, что при вставке на удаленный сервер, SQL Server выполняет следующие операции:
  • Открывает распределенную транзакцию, включая режим неявных транзакций.
  • Открывает курсор и вставляет записи последовательно одну за другой, а не все сразу, как в случае обычной вставки, без использования linked-серверов.
Последовательную вставку каждой записи также можно заметить, если на целевом сервере создать триггер на INSERT – триггер будет срабатывать столько раз, сколько записей существует во вставляемом наборе.
Для примера – следующий триггер на таблице Customers:

CREATE TRIGGER dbo.I_Customers
   ON  dbo.Customers
   AFTER INSERT
AS 
BEGIN
 declare @InsertedCount int, @TotalRows int
 select @InsertedCount = count (*) from inserted
 select @TotalRows = count (*) from Customers

 insert into CallsCount (InsertedCount, TotalRows)
 values (@InsertedCount, @TotalRows)
END
В этом триггере CallsCount - это вспомогательная таблица, которая поможет подсчитать сколько раз триггер был запущен. Она содержит два поля – InsertedCount – кол-во записей в таблице inserted и TotalRows – общее кол-во записей в таблице Customers на момент срабатывания триггера.
После выполнения предыдущего запроса на вставку выборка из таблицы CallsCount дает следующий результат:
InstertedCount TotalRows
1 1
1 2
1 3
Здесь видно, что триггер был вызван 3 раза. При этом каждый раз в таблице inserted была одна запись, а кол-во записей в таблице Customers увеличивалось постепенно. Это еще раз доказывает то, что записи вставляются последовательно, одна за одной.
Можно провести интересный эксперимент, изменив триггер следующим образом:

ALTER TRIGGER dbo.I_Customers 
   ON  dbo.Customers
   AFTER INSERT
AS 
BEGIN
 declare @InsertedCount int, @TotalRows int
 select @InsertedCount = count (*) from inserted
 select @TotalRows = count (*) from Customers

 insert into CallsCount (InsertedCount, TotalRows)
 values (@InsertedCount, @TotalRows)

 if @TotalRows = 3
  rollback tran
END
Т.е. таким образом можно как бы попытаться откатить вставку третьей строки. Однако, после запуска запроса на вставку, при наличии такого триггера, обе таблицы – Customers и CallsCount будут пустыми – в них не будет не только третьей записи, но и вообще ни одной.
Причина этого видна в перехваченной последовательности запросов - SQL Server объявляет распределенную транзакцию. Вызывая rollback tran в триггере мы откатываем не только данный триггер, но и всю транзакцию вставки. Это, вообще говоря, логично, так как при этом работа триггера выглядит так же как и при вставке не из удаленного сервера, с той лишь разницей, что вызывается он несколько раз. Именно для обработки таких ситуаций SQL Server и объявляет распределенную транзакцию. Побочным эффектом такой транзакции является длительная блокировка таблицы Customers на целевом сервере на все время вставки.
Все эти эффекты не имеют важного значения, если между серверами передаются небольшие объемы данных. Однако, последовательная вставка курсором большого кол-ва записей может занимать довольно длительное время, добавляя ко всему прочему блокировку таблиц на все это время. В качестве примера можно попробовать передать большой справочник, состоящий примерно из 130 000 строк.

insert into DestServer.Northwind.dbo.Dictionary
select *
from Dictionary
На тестовом сервере это привело к 130 000 запросам на вставку и заняло примерно 370 секунд. В дальнейшем это значение будет сопоставлено с результатами работы технологии Pull.


Технология Pull
При использовании технологии pull запрос на вставку данных выполняется на целевом сервере. При этом происходит «втягивание» данных от сервера-источника.
Такой запрос, выполняющийся на целевом сервере, выглядит так:

insert into Customers
select *
from SourceServer.Northwind.dbo.Customers
Если воспользоваться SQL Server Profiler, то на целевом сервере он будет выполняться в неизменном виде -

insert into Customers
select *
from SourceServer.Northwind.dbo.Customers
go
На сервере-источнике при этом будет выполнен следующий запрос -

set implicit_transactions on 
go

declare @P1 int
set @P1=1
declare @P2 bigint
set @P2=8381016049028902
exec sp_getschemalock @P1 output, @P2 output, 
N'"Northwind"."dbo"."Customers"'
select @P1, @P2
go

declare @P1 int
set @P1=2
exec sp_prepexec @P1 output, NULL, 
N'SELECT Col1028,Col1027,Col1026,Col1025,Col1024,
Col1023,Col1022,Col1021,Col1020,Col1019,Col1018 FROM 
(SELECT Tbl1001."CustomerID" Col1018,
        Tbl1001."CompanyName" Col1019,
        Tbl1001."ContactName" Col1020, 
        Tbl1001."ContactTitle" Col1021,
        Tbl1001."Address" Col1022, 
        Tbl1001."City" Col1023,
        Tbl1001."Region" Col1024,
        Tbl1001."PostalCode" Col1025,
        Tbl1001."Country" Col1026,
        Tbl1001."Phone" Col1027,
        Tbl1001."Fax" Col1028 
 FROM "Northwind"."dbo"."Customers" Tbl1001) Qry1029'
select @P1
go

exec sp_unprepare 2
go

exec sp_releaseschemalock 1
go

IF @@TRANCOUNT > 0 ROLLBACK TRAN
Go

set implicit_transactions off 
go
Здесь видно, что при такой технологии курсор не используется. Вместо этого применяется пакетная вставка записей – всех за один раз. Это же доказывает нам и использование триггера. После выполнения запроса таблица CallsCount содержит всего одну запись -
InstertedCount TotalRows
3 3
Из этого также видно, что в запрос выполнялся всего один раз, в таблице insterted было 3 записи и в саму таблицу Customers также было вставлено 3 записи.

insert into Dictionary
select *
from SourceServer.Northwind.dbo.Dictionary
Если попытаться замерить время, которое сервер тратит на вставку записей таким образом на примере большого справочника из 130 000 строк, то следующий запрос на том же сервере выполняется 30 секунд, что в 12 раз быстрее, чем при использовании технологии Push.



Сравнительные данные
В следующей таблице приведены сравнительные данные этих двух технологий
Push Pull
Запрос Выполняется на сервере-источнике.
Пример:
insert into DestServer.Northwind.dbo.Customers
select * from Customers
Выполняется на сервере-приемнике.
Пример:
insert into Customers
select * from SourceServer.Northwind.dbo.Customers
Вставка Курсором, по одной записи. Пакетно. Все записи за один запрос.
Триггер Срабатывает для каждой вставляемой записи. Срабатывает один раз для всех вставляемых записей.
Скорость Низкая, так как записи вставляются одна за одной. Высокая (в десятки раз быстрее), так как все записи вставляются за один запрос.


Заключение
Из всего вышесказанного следует, что технология pull обеспечивает более высокую производительность, чем технология push и является более предпочтительной.
Я с радостью рассмотрю любые комментарии и вопросы по данной тематике.
Мои координаты доступны на сайте www.msmirnov.ru
Михаил Смирнов
Руководитель проектов.
Мой сайт - www.msmirnov.ru

среда, 6 декабря 2006 г. - www.msmirnov.ru

Управление ошибками на практике

Здесь я привожу копию моей статьи "Управление ошибками на практике", опубликованной в журнале RSDN Magazine #3 за 2006г. http://rsdn.ru/?article/methodologies/errorpractice.xml

Управление ошибками на практике.

В этой статье я расскажу о своем опыте внедрения формализованного процесса управления ошибками.
Основная задача статьи – показать важность организации такого процесса и дать начинающим руководителям разработки набор рекомендаций по его построению.

Введение

Обычно потребность в формализованном процессе управления ошибками не возникает при выполнении небольших проектов. Проект длительностью в 1-2 человеко-месяца вполне может обойтись и без него.
Однако проекты большего объема, особенно если продукт развивается постоянно, нуждаются в подобном процессе. Начиная с некоторого момента, отсутствие формализации в управлении ошибками начинает существенно влиять на ход разработки.
В данный момент я руковожу проектом, штат которого с течением времени вырос от 2 человек до 12 человек. В начале, когда основными работами по проекту была разработка концепции и архитектуры, в формализованном подходе к управлению ошибками не было необходимости. Затем, на фазе кодирования, он стал необходим. Именно на эту фазу приходится наибольший объем кодирования и тестирования, и именно поэтому необходимость управления ошибками становится столь очевидной.

Проблемы, возникающие при отсутствии управления ошибками

В фазе кодирования стало ясно, что отсутствие формализованного подхода приводит к следующим проблемам:
  1. Разработчики начинают забывать об ошибках. Такое часто бывает, если ошибок достаточно много. Двигаясь вперед, устраняя эти ошибки, он может пропустить некоторые из них, посчитав их низкоприоритетными, а потом забыть к ним вернуться.
  2. Тестировщики забывают проверять качество устранения ошибок. Если разработчик исправил ошибку и попросил тестировщика проверить это, то, во-первых, тестировщик уже может не помнить, в чем она состояла (или забыть детали) и, во-вторых, также может забыть выполнить проверку, переключившись на ошибки других разработчиков.
  3. Разработчики устраняют ошибки «не в том порядке». Довольно часто такое может быть, когда разработчику хочется устранять не те ошибки, которые являются важными с точки зрения заказчика или руководителя проекта. Если в команде мало разработчиков (1-2), то, в принципе, они могут напрямую контактировать с пользователями, чтобы выяснять приоритет ошибок, но даже в этом случае они могут не знать всей полноты картины.
  4. Руководитель не знает, сколько ошибок ждут устранения, и сколько исправлено. Очевидно, что если информация об ошибках поступает в устном виде, то очень трудно отследить прогресс их устранения. Руководителю проекта в такой ситуации очень трудно понять, что происходит. Ему неизвестно, сколько еще ошибок ждут своей очереди, какие из них являются важными, а какие – нет, какие из них можно отложить, а каким надо уделить первоочередное внимание.
Внедрение формализованного процесса помогло решить подобные проблемы.

Описание процесса

Основными участниками такого процесса являются:
  • Тестировщик.
  • Руководитель команды.
  • Разработчики.
Проще всего описать процесс управления ошибками через последовательность действий, выполняемых над ошибкой в рамках такого процесса.
ШагУчастникДействиеРезультат
1.ТестировщикОбнаруживает ошибку и вносит ее в систему управления ошибками.
1.Заказчик или пользователь.Сообщает об ошибке и вносит ее в систему управления ошибками.
1.РазработчикЗамечает ошибку у себя или у другого разработчика и вносит ее в систему управления ошибками.Ошибка внесена в систему управления ошибками.
2.Руководитель команды и иногда тестировщикОпределяет приоритет ошибки и назначает разработчика, ответственного за ее устранение.Ошибка поступает к разработчику.
3.РазработчикУстраняет ошибку и помечает ее как исправленную.Ошибка поступает к тестировщику на проверку.
4ТестировщикПроверяет правильность устранения ошибки и, если ошибка не устранена или устранена неверно, то вновь направляет ее к разработчику, а если все в порядке, то закрывает ошибку.Ошибка прекращает свое существование или отправляется на повторную доработку.
Здесь видно, как ошибка от обнаружения до устранения переходит от одного члена команды к другому.
Центральным звеном является система управления ошибками (Bug Tracking). Без нее эффективное внедрение такого процесса невозможно.
Описанные выше процесс является упрощенной моделью, в его организации могут быть изменения, например:
  1. Пользователи не обязательно должны сами вносить ошибки в базу. За них это могут осуществлять специалисты службы поддержки. Кроме того, сам продукт в процессе работы может автоматически вносить сообщения в базу ошибок. Это, конечно, возможно при условии, что система управления ошибками имеет API.
  2. Необязательно именно Team Leader должен заниматься распределением ошибок. Если команда работает достаточно тесно, то тестировщик должен знать, кто из разработчиков отвечает за компонент системы, содержащий данную конкретную ошибку. В этом случае он может самостоятельно провести назначение.
  3. Некоторые ошибки могут оказаться трудными для проверки тестировщиком – например, ошибки времени исполнения в Windows-сервисах. Такие ошибки можно проверять, используя unit-тестирование или привлекая других разработчиков. При unit-тестировании также может происходить автоматическое наполнение базы ошибок.
  4. Ошибкам не обязательно должен быть назначен ответственный за их устранение разработчик. Team Leader может посчитать некоторые ошибки несущественными и отложить их исправление на потом, пометив специальным образом.
Для организации подобного процесса следует также соблюдать следующие условия:
  1. Каждый отчет об ошибке должен содержать необходимый набор свойств. Это поможет всем членам команды ориентироваться в списке ошибок. Как мне кажется, следующего набора свойств достаточно для начала организации процесса:
  • Приоритет – Низкий, Средний, Высокий, Критический.
  • Тип ошибки – Exception, Дизайн, Неверная функциональность и т.п.
  • Компонент продукта – сайт, база данных и т.п.
  • Версия компонента.
  1. Чтобы разработчикам не требовалось тратить время на повторение ошибок, тестировщикам необходимо описывать ошибки как можно более подробно. Желательно, каждую ошибку должны сопровождать:
  • Снимок экрана (если возможно).
  • Информации о пользователе (например, логин для сайта), сообщившем об ошибке.
  • URL, если речь идет о сайте.
  • Стек, если речь идет об ошибке времени выполнения.
  • Последовательность действий, приводящая к ошибке.
  • Файлы отчетов об ошибках, письма пользователей, тексты ошибок и т.д.
  1. Необходимо заранее обеспечить каждому разработчику возможность просмотра списка ошибок, назначенных ему, а также общего списка, создав специальные отчеты и т.п. Тестировщикам следует обеспечить быстрый доступ к списку ошибок, ожидающих проверки.
  2. Хорошо зарекомендовала себя практика давать возможность разработчикам назначать других разработчиков ответственными за ошибки. В этом случае разработчики смогут передавать ошибки друг другу на рассмотрение, если в устранении задействовано несколько человек.
  3. Следует ограничить возможность разработчиков удалять или закрывать ошибки. Такое право должно быть только у тестировщика и руководителя. Это следует сделать, чтобы исключить возможность умышленного сокрытия ошибки.
  4. Разработчики при отметке об исправлении ошибки должны указывать время, потраченное на ее устранение. В дальнейшем это поможет получать разнообразные аналитические отчеты, например:
  • процент времени, затрачиваемый на устранение ошибок;
  • средние затраты времени на устранение ошибки;
  • среднее время жизни ошибки.
  1. Если пользователи продукта хотят самостоятельно составлять отчеты об ошибках и отслеживать их устранение, то им стоит предоставить web-интерфейс системы управления ошибками. Для внутреннего использования обычно удобнее Windows-интерфейс. Поэтому при выборе системы управления ошибками стоит обратить внимание на то, чтобы она предоставляла оба способа доступа.
Подобный процесс управления ошибками очень хорошо согласуется с идеей ежедневных сборок всей системы. При этом после сборки и тестового развертывания:
  1. Тестировщики получают новую порцию устраненных ошибок для верификации.
  2. Проводя регрессионное тестирование продукта, тестировщики проверяют, не повлияло ли устранение ошибок на работу продукта. Если в работе продукта появились проблемы – помещают их в базу ошибок. Опять же это может быть автоматизировано.
  3. Руководитель разработчиков знает, какая порция ошибок была исправлена в текущей сборке продукта. Иногда это может служить некоторой формой отчетности перед заказчиком.
  4. Оценивая критичность ошибок, которые в данный момент устраняются разработчиками, их руководитель может отложить тестовую сборку до устранения этих ошибок.
При выборе системы управления ошибками (bugtracking-системы), стоит обратить внимание на следующие характеристики:
  1. Интерфейс. Система должна предоставлять как web-, так и windows-интерфейс. Обычно для работы внешних пользователей лучше подходит web-интерфейс, а для внутренних – windows.
  2. Наличие API. Используя API, вы сможете помещать новые ошибки в базу при автоматизированном тестировании.
  3. Возможности конфигурации. Скорее всего, ваш процесс управления ошибками будет иметь свои нюансы и будет отличаться от того, который предлагается по умолчанию системой. Поэтому системы должны предоставлять богатые возможности по настройке последовательности процесса, его шагов, прав разработчиков и т.п.
  4. Первоначальный опыт показал, что не стоит использовать MS Project Server в качестве подобной системы. Его предназначение состоит в управлении более объемными задачами, чем мелкие ошибки.

Заключение

В этой статье я дал ряд рекомендаций по организации процесса управления ошибками. Конечно, это только схематичное описание процесса. Каждый конкретный случай, скорее всего, будет иметь существенные отличия. Однако такой подход показал свою эффективность в моем опыте управления разработкой.
Я с радостью рассмотрю любые комментарии и вопросы по данной тематике. Мои координаты доступны на сайте http://www.msmirnov.ru/ или по электронной почте msmirnov@msmirnov.ru
Мой сайт - www.msmirnov.ru