вторник, 28 сентября 2010 г.

Fix vs Feature

Все рано или поздно сталкиваются с тем, что в продукте на стороне заказчика возникают ошибки. Бывают они разного уровня критичности и у разного количества заказчиков. Зачастую реакция на такие запросы требует оперативности и делается в достаточно нервных условиях.

Мне бы хотелось выразить свою точку зрения на то что есть 'фикс' и чем он отличается от полноценной 'фичи'.

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

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

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

В любом случае перед тем как делать что-нибудь стоит ответить на следующие вопросы:

  • Знаем ли мы как это быстро поправить?
  • Существует ли способ обойти ситуацию без изменении в коде? (workaround)?
  • Сильно ли эта ситуация мешает заказчику?
  • Много ли заказчиков столкнулось с этой ситуацией?
  • Уверены ли мы отсутствии побочных эффектов после правки?
  • Есть ли у нас всеобъемлющее решение чтобы поправить эту и подобные ситуации в будущем?

Имея эти ответы уже можно думать о том делать ли private fix (только для этого заказчика), public fix (для всех), учесть это проблему и вписать ее в release notes (defer) или же внести в планы будущих релизов полноценную фичу по комплексному устранению этой проблемы (feature).

Цена у всех этих решении разная.

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

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

пятница, 24 сентября 2010 г.

C#: Cash data by time

Часто возникает ситуация когда надо хранить кэш данных, к которым наиболее часто обращаются. Стандартной реализации такой штуки в .NET я не нашел, вот и решил сам поупражняться.
    public class TimeCashDictionary<TKey, TValue> : IDictionary<TKey,TValue>
    {
        protected class TimedValue
        {
            public TimedValue(TValue value)
            {
                LastAccess = DateTime.UtcNow;
                Value = value;
            }
            public TValue Value {get;set;}
            public DateTime LastAccess { get; set; }
        }

        Dictionary<TKey, TimedValue> m_Dict;
        int m_CashSize;
        public int CashSize { get { return m_CashSize; } }

        public TimeCashDictionary(int cashSize)
        {
            m_CashSize = cashSize;
            m_Dict = new Dictionary<TKey, TimedValue>(cashSize+1);
        }

        private void RemoveOldWhenNeed()
        {
            if (m_Dict.Count >= m_CashSize)
            {
                m_Dict.Remove(m_Dict.OrderBy(kv => kv.Value.LastAccess).First().Key);
            }
        }
           
        public void Add(TKey key, TValue value)
        {
            RemoveOldWhenNeed();
            m_Dict.Add(key, new TimedValue(value));
        }

        public bool ContainsKey(TKey key)
        {
            return m_Dict.ContainsKey(key);
        }

        public ICollection<TKey> Keys
        {
            get { return m_Dict.Keys; }
        }

        public bool Remove(TKey key)
        {
            return m_Dict.Remove(key);
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            TimedValue val;
            var res = m_Dict.TryGetValue(key,out val);
            value = res?val.Value:default(TValue);
            if (res)
                val.LastAccess = DateTime.UtcNow;

            return res;
        }

        public ICollection<TValue> Values
        {
            get
            {
                return  new List<TValue>(m_Dict.Values.Select(el => el.Value));
            }
        }

        public TValue this[TKey key]
        {
            get
            {
                var val = m_Dict[key];
                val.LastAccess = DateTime.UtcNow;
                return val.Value;
            }
            set
            {
                RemoveOldWhenNeed();
                m_Dict[key] = new TimedValue(value);
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            RemoveOldWhenNeed();
            (m_Dict as ICollection<KeyValuePair<TKey, TimedValue>>).Add(new KeyValuePair<TKey, TimedValue>(item.Key, new TimedValue(item.Value)));
        }

        public void Clear()
        {
            m_Dict.Clear();
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return (m_Dict as ICollection<KeyValuePair<TKey, TimedValue>>).Contains(new KeyValuePair<TKey, TimedValue>(item.Key, new TimedValue(item.Value)));
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public int Count
        {
            get { return m_Dict.Count; }
        }

        public bool IsReadOnly
        {
            get { return (m_Dict as ICollection<KeyValuePair<TKey, TimedValue>>).IsReadOnly; }
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return (m_Dict as ICollection<KeyValuePair<TKey, TimedValue>>).Remove(new KeyValuePair<TKey,TimedValue>(item.Key,new TimedValue(item.Value)));
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return m_Dict.Select(kv => new KeyValuePair<TKey, TValue>(kv.Key, kv.Value.Value)).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {

            return m_Dict.GetEnumerator();
        }
    }
 
Используется так:
var tc = new TimeCashDictionary<string, string>(3);

tc["sasha"] = "1";
tc["dima"] = "2";
tc["mike"] = "3";
var  v = tc["sasha"];
tc["katy"] = "4";
tc["max"] = "5";
 

Результатом будет:
sasha,katy,max

Версия сырая и на крутизну использования C# не претендующая, но если вдруг приглянется можете использовать под CC лицензией. :)

среда, 1 сентября 2010 г.

SharePoint 2010: Error occurred in deployment step 'Activate Features'

Решил посмотреть, что нового в 2010 студии для работы с SharePoint 2010, и сходу уперся в ошибку "Error occurred in deployment step 'Activate Features': Feature with Id" на моем тестовом портале.
Даже если в "Solutions Galery" пытаешься выполнить deploy ,он повисает в статусе deploying. Проверил и увеличил права аккаунта под которым крутится OWSTIMER, почитал логи SharePoint и всё тщетно.

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

Стоило фронтенд отключить от фермы как все замечательно заработало.