вторник, 13 декабря 2011 г.

Celery loader hijacks/replaces standard logging configuration

Оказывается celeryd загрузчик перекрывает стандартную logging конфигурацию для того чтобы облегчить жизнь разработчику.

Иинциатива конечно хорошая, однако для нас это было полной неожиданностью.

К счастью есть как минимум два способа изменить это поведение.

Быстрое, но не совсем полное решение это установить CELERYD_HIJACK_ROOT_LOGGER = False.

Чуть более сложное, но концептуально верное решение это использовать setup-logging хук/сигнал, который предоставляет Celery.

Ссылки:

In 2.1 logging behavior was changed to not configure logging if it was already configured. The problem is that some libraries does not play nice and hijack the root logger, or use logging.basicConfig – resulting in users not getting any output or logs.
http://readthedocs.org/docs/celery/en/release21-maint/changelog.html#version-2-1-4

By default any previously configured logging options will be reset, because the Celery programs “hijacks” the root logger.
If you want to customize your own logging then you can disable this behavior.
http://ask.github.com/celery/configuration.html#celeryd-hijack-root-logger

setup_logging signal.
Celery won’t configure the loggers if this signal is connected, so you can use this to completely override the logging configuration with your own.
http://ask.github.com/celery/userguide/signals.html#setup-logging

воскресенье, 16 октября 2011 г.

SQLAlchemy for Python. Some queries examples including generated SQL

UPDATE on 2011-10-18. Ответил на некоторые комментарии прямо внизу поста.

Не так давно мне посчастливилось организовать technology validation самого продвинутого ORM-фреймверка для языка Python, который называется SQLAlchemy.

Признаюсь что перед стартом этого процесса я был достаточно скептически настроен, у меня была уверенность что я не увижу функционала, который бы хотя бы немного дотягивал до Java/Hibernate или .NET/NHibernate.

На моем последнем .NET-проекте, который назывался TravelConfirm, я предложил использовать NHibernate, о чем ни разу не жалею.

Более того, рекомендую всем сомневающимся внедрять его везде, где бы вы планировали использовать Entity Framework. NHibernate в связке с Fluent NHibernate прекрасно показали себя в работе с MySQL и Amazon RDS несмотря на то, что мы использовали в том числе и хранимые процедуры.

Но давайте вернемся к SQLAlchemy.

Статья на Wikipedia гласит что SQLAlchemy это open source SQL toolkit и object relational mapper, первый релиз которого был в феврале 2006-го года и что проект лицензируется под достаточно лояльной open source лицензией MIT.
http://en.wikipedia.org/wiki/SQLAlchemy

SQLAlchemy поддерживает довольно внушительный список СУБД, который в первом приближении сопоставим с таковым у Java/Hibernate:
  • Drizzle
  • Firebird
  • IBM DB2 / Informix
  • MaxDB
  • Microsoft Access
  • Microsoft SQL Server
  • MySQL
  • Oracle
  • PostgreSQL
  • SQLite
  • Sybase

Актуальную информацию по поддерживаемым диалектам можно увидеть тут:
http://www.sqlalchemy.org/docs/dialects/index.html

Еще одна особенность, которую я постояно наблюдаю в стеке Python, так это огромное изобилие библиотек, функциональность которых перекрывает, а иногда даже дублирует друг друга. Это вносит некоторую дополнительную головную боль разработчику, но это "правильная вещь". В рамках SQLAlchemy это выражается в том, что диалект каждой из представленных СУБД умеет работать поверх нескольких реализаций DB-API драйверов.

Например с MySQL я могу работать используя MySQL Connector/J, MySQL Connector/Python, mysql-python, OurSQL либо pymysql.
Подробную информацию о поддерживаемых диалектах и DB-API драйверах можно увидеть по следующей ссылке:
http://www.sqlalchemy.org/docs/core/engines.html#supported-databases

Думаю что вводной информации более чем достаточно, перейдем к делу. Ниже я буду приводить набор Python-тестов, которые используют ту или иную функциональность SQLAlchemy, сразу после кода теста будет приводится SQL запрос к SQLite базе, который генерирует SQLAlchemy.

Хочу сделать акцент на том, что SQLAlchemy меня просто поразил чистотой генерируемого SQL-кода, который часто превосходит даже такого титана как NHibernate. Код приводится как есть, я только лишь расставил переносы строк и отступы для лучшей читаемости - в оригинале все эти запросы формируются в несколько длинных строк. Точно так же как это делает NHibernate.

Нативный SQL-запроса с параметрами:
def test_query_with_from_native_sql_with_parameters(self):
  with DbSession() as session:
   result = session.query(User)\
   .from_statement('SELECT * FROM user where name=:name')\
   .params(name='User 4')\
   .all()
Generated SQL:
SELECT * FROM user where name=?

Извлечение пользователей с сортировкой по трем параметрам в разных направлениях:
def test_query_with_order_by_several_columns(self):
  with DbSession() as session:
   result = session.query(User)\
   .order_by(User.password)\
   .order_by(User.name.desc())\
   .order_by(User.age)\
   .all()
Generated SQL:
SELECT 
  user.user_id AS user_user_id, 
  user.name AS user_name, 
  user.age AS user_age, 
  user.password AS user_password 
 FROM user 
 ORDER BY 
  user.password, 
  user.name DESC, 
  user.age

Извлечение пользователя, который первым попался под руку:
def test_query_with_first(self):
  with DbSession() as session:
   result = session.query(User).first()
Generated SQL:
SELECT 
  user.user_id AS user_user_id, 
  user.name AS user_name, 
  user.age AS user_age, 
  user.password AS user_password 
 FROM user
  LIMIT ? OFFSET ?

Извлечение всех уникальных имен пользователей:
def test_query_with_distinct(self):
  with DbSession() as session:
   result = session.query(User.name).distinct().all()
Generated SQL:
SELECT DISTINCT user.name AS user_name
FROM user

Использование традиционных агрегирующих функций:
def test_query_with_max_min_avg_count(self):
  with DbSession() as session:
   max_user_age = session.query(func.max(User.age)).scalar()
   min_user_age = session.query(func.min(User.age)).scalar()
   avg_user_age = session.query(func.avg(User.age)).scalar()
   count_user_age = session.query(func.count(User.age)).scalar()
Generated SQL:
SELECT max(user.age) AS max_1 
 FROM user
 
 SELECT min(user.age) AS min_1 
 FROM user
 
 SELECT avg(user.age) AS avg_1 
 FROM user
 
 SELECT count(user.age) AS count_1 
 FROM user

Извлечение с группировкой:
def test_query_with_group_by(self):
        with DbSession() as session:
            result = session.query(User.name, func.count(User.user_id))\
    .group_by(User.name)\
    .all()
Generated SQL:
SELECT 
  user.name AS user_name, 
  count(user.user_id) AS count_1 
 FROM user 
 GROUP BY user.name

Надуманный вариант с подзапросом вместо традиционного join'а:
def test_query_with_subquery(self):
  with DbSession() as session:
   subquery = session.query(User.name).filter(User.age > 21).subquery()
   result = session.query(Developer).join(subquery, subquery.c.name == Developer.name).all()
Generated SQL:
SELECT 
  developer.developer_id AS developer_developer_id, 
  developer.name AS developer_name 
 FROM developer JOIN (SELECT user.name AS name 
 FROM user 
 WHERE user.age > ?) AS anon_1 ON anon_1.name = developer.name


Выборка данных с тремя inner join'ами:
def test_query_which_join_three_tables(self):
  with DbSession() as session:
   result = session.query(User, Developer, Parent)\
   .join(Developer, Developer.name == User.name)\
   .join(Parent, Developer.name == Parent.name)\
   .all()
Generated SQL:
SELECT 
  user.user_id AS user_user_id, 
  user.name AS user_name, 
  user.age AS user_age, 
  user.password AS user_password, 
  developer.developer_id AS developer_developer_id, 
  developer.name AS developer_name, 
  parent.parent_id AS parent_parent_id, 
  parent.name AS parent_name 
 FROM user 
  JOIN developer ON developer.name = user.name 
  JOIN parent ON developer.name = parent.name

Выборка данных с одним jeft outer join'ом:
def test_query_with_left_outer_join(self):
  with DbSession() as session:
   result = session.query(Developer, Parent)\
   .outerjoin(Parent, Parent.name == Developer.name)\
   .all()
Generated SQL:
SELECT 
  developer.developer_id AS developer_developer_id, 
  developer.name AS developer_name, 
  parent.parent_id AS parent_parent_id, 
  parent.name AS parent_name 
 FROM developer 
  LEFT OUTER JOIN parent ON parent.name = developer.name

Ограничение выборки используя методы limit и offset из querying DSL:
def test_query_with_limit_offset(self):
  with DbSession() as session:
   result = session.query(User).order_by(User.name).limit(1).offset(1).all()
Generated SQL:
SELECT 
  user.user_id AS user_user_id, 
  user.name AS user_name, 
  user.age AS user_age, 
  user.password AS user_password 
 FROM user ORDER BY user.name
  LIMIT ? OFFSET ?

Ограничение выборки используя метод slice с двумя параметрами из querying DSL. Вероятно в каком-то из диалектов метод slice может быть более эффективен:
def test_query_with_slice(self):
  with DbSession() as session:
   result = session.query(User).order_by(User.name).slice(start=1, stop=2).all()
Generated SQL:
SELECT 
  user.user_id AS user_user_id, 
  user.name AS user_name, 
  user.age AS user_age, 
  user.password AS user_password 
 FROM user 
 ORDER BY user.name
  LIMIT ? OFFSET ?

Объединение нескольких запросов через использование union:
def test_query_with_union_selects_distinct_records(self):
  with DbSession() as session:
   query1 = session.query(User.name)
   query2 = session.query(Developer.name)
   query3 = session.query(Parent.name)
   result = query1.union(query2, query3).all()
Generated SQL:
SELECT anon_1.user_name AS anon_1_user_name 
 FROM (SELECT user.name AS user_name 
 FROM user UNION SELECT developer.name AS developer_name 
 FROM developer UNION SELECT parent.name AS parent_name 
 FROM parent) AS anon_1

Объединение нескольких запросов через использование union all:
def test_query_with_union_all(self):
  with DbSession() as session:
   query1 = session.query(User.name,)
   query2 = session.query(Developer.name)
   query3 = session.query(Parent.name)
   result = query1.union_all(query2, query3).all()
Generated SQL:
SELECT anon_1.user_name AS anon_1_user_name 
 FROM (SELECT user.name AS user_name 
 FROM user UNION ALL SELECT developer.name AS developer_name 
 FROM developer UNION ALL SELECT parent.name AS parent_name 
 FROM parent) AS anon_1


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

Более того, DSL для построения запросов в SQLAlchemy даже умеет ряд вещей, которых я не видел ни в NHibernate ни в Linq, а именно:
  • подзапросы
  • предложения UNION, UNION ALL, EXCEPT, EXCEPT ALL, INTERSECT, INTERSECT ALL, HAVING, CASE
  • indexing hint'ы
  • bulk insert для любой СУБД (у NHibernate есть ограниченные возможности, причем работают только в Microsoft SQL Server)
  • bulk update/delete. При этом database session/unit of work не трекает изменений, но не смотря на это такая функциональность бывает нужна приложению

SQLAlchemy поддерживает три основных варианта мэппинга иерархии классов аналогично NHibernate, правда они используют немного другие термины.
У SQLAlchemy термины Single Table Inheritance, Joined Table Inheritance, Concrete Table Inheritance соответствуют терминам Table per class hierarchy, Table per subclass, Table per concrete class из NHibernate:
http://www.sqlalchemy.org/docs/orm/inheritance.html

Более детально ознакомится с функциональными возможностями и их использованием можно используя официальную документацию SQLAlchemy:
http://www.sqlalchemy.org/docs/


UPDATE on 2011-10-18. Ответил на некоторые комментарии прямо внизу поста.

> Что с Foreign Keys? (mapping/использование/генерируемый SQL)

GUID. Поддержка реализована в первую очередь для PostgreSQL, но она не сложная и может быть расширена для других СУБД:
http://www.sqlalchemy.org/docs/core/types.html#backend-agnostic-guid-type

GUID.Comb не поддерживается, так что если алгоритм очень нужен, то придется его портировать с NHibernate.

HiLo генератор так же не реализован. Есть пример кастомного генератора для database shards, где говорится что его можно использовать как прототип для HiLo генератора:
https://bitbucket.org/sqlalchemy/sqlalchemy/src/54ee83285eef/examples/sharding/attribute_shard.py

Post Insert генераторы не выделены явно, однако поддерживаются по-умолчанию. Identity, Guid.Native и любой другой генератор, который вычисляется на стороне БД декларируется одинаково:
http://www.sqlalchemy.org/docs/core/schema.html#server-side-defaults

Database Sequence генератор:
http://www.sqlalchemy.org/docs/core/schema.html#defining-sequences

Composite Primary Key генератор как частный случай Assigned Key генератора:
http://stackoverflow.com/questions/2374243/is-it-possible-to-get-sqlalchemy-to-create-a-composite-primary-key-with-an-integ

К сожалению привести примеры SQL не могу, так как для этого нужно делать тесты.


> 2nd layer cache (memcached)?

SQLAlchemy умеет работать поверх Beaker Caching, который отправляет NHibernate в тяжелый нокаут по количеству поддерживаемых cache-бекендов.
С другой стороны, SQLAlchemy предоставляет более явный Caching API, что одновременно и хорошо и плохо - все зависит от личных предпочтений:
http://www.sqlalchemy.org/docs/orm/examples.html#beaker-caching


> Подзапросы в NH не так красиво как в Py, но всё же

Если быть более точным, то поздапросы в NHibernate работают только при использовании QueryOver и недоступны в Linq.


> NHibernate {bulk/batching} на вскидку 2 варианта и с недавнего (3.2) времени дэфолтный:

Bulk insert и query batching это немного разные вещи. Насколько эффективно реализован query batching в SQLAlchemy сказать не могу. Но вот bulk insert в SQLAlchemy реализован удобно, просто и не зависит от СУБД. При этом NHibernate поддерживает bulk insert только у Microsoft SQL Server'а, поскольку эта функциональность упирается в возможности нижележащего ADO.NET Data Provider'а.

пятница, 30 сентября 2011 г.

Кто собирал Twisted для Python под Windows 64 bit?

Фух. Сегодня выдался нелегкий денек...

Оказалось что фреймверк Twisted не собран под Windows 64. Пробовали MinGW, потом MinGW 64, потом Cygwin, потом MSVC. К сожалению пока так ничего и не получилось :(

Ознакомились с модулем logging. Нашли библиотеку которая позволит отправлять логи на нашу любимую Log2Console в формате log4j XML.

Продолжали разбираться с SQLAlchemy ORM фреймверком. Оказывается он умеет делать bulk delete/update, чего не умеет делать даже NHibernate.

В понедельник еще попробую помучать Twisted, есть еще одна идея как его собрать под Windows 64 bit...

Если удастся собрать, то тогда будет возможность посмотреть на Python logging server, собственно ради него и затеяли эту возню с Twisted :)

среда, 28 сентября 2011 г.

Извлечение файлов из .msi архивов

По определенным причинам я очень не люблю инсталляционные пакеты в Windows и предпочитаю работать с софтом в режиме "распаковал и запустил".

Долгое время использовал прием описанный в этой статье:
Howto: extract files from a .msi file using the Windows command line

Однако с помощью этой команды в некоторых случаях мне не удавалось распаковать файлы из .msi архива.

Сегодня читая комментарии на StackOverflow наткнулся на проект lessmsi.

Пост на StackOverflow:
how can I manually install the Office 2007 PIAs on a computer with no Office installed?

lessmsi - A tool to view and extract the contents of an Windows Installer (.msi) file:
http://code.google.com/p/lessmsi/

Пока не пробовал его в работе, но подумал, вдруг кому пригодится.

четверг, 8 сентября 2011 г.

Погружаясь в разработку на Python...

Последнее время промышляю написанием скриптов, которые автоматиризуют различные рутиные операции:
- правка файлов AssemblyInfo.cs с целью внесения build number, git branch name, git commit hash
- компиляция Visual Studio 2010 solution с проектами на C#/.NET 4.0
- анализ кода с помощью утилиты Gendarme
- подготовка deployment архивов, в которые складываем все бинарники за исключением XML-файлов с документационными комментариями, временных файлов, лог-файлов
- сбор всех тикетов Redmine для текущего билда и внесение соответсвующего комментария в каждый из этих тикетов

Планирую еще добавить запуск NUnit тестов в связке с PartCover, подготовку Test Code Coverage отчета в HTML-формате.

Сейчас Code Coverage метрики мы вообще не собираем - никак не дойдут руки добавить, а запуск NUnit тестов для нас делает TeamCity.

Хочу прийти к тому чтобы разработчик или QA имел возможность локально запустить билд скрипт, который бы выполнил всю ту же работу, которая происходит на CI-сервере TeamCity.


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

Среди кандидатов еще рассматривались Cygwin + Bash, PowerShell, IronRuby + Rake, IronPython.

Cygwin + Bach кроссплатформенные, однако язык Bash за счет своей долгой истории развития выглядит достаточно несогласованным в плане синтаксиса. На нем не очень удобно писать императивный код и я не видел чтобы кто-то использовал там дебаггер.

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

IronRuby + Rake можно расценивать как готовую билд систему с хорошим императивным языком. Если бы у меня не было работы с язком Pyhton, то, возможно, я бы выбрал именно этот вариант.

Python думаю не нужен в особой рекламе. Достаточно сказать что я его выбрал :) Ну а если серьезно, язык я выбрал для решения поставленных задач из-за того что он довольно популярен, решает задачи общего назначения, кросплатформенный, используется для разработки как Desktop так и Web-решений. Так же его используют для нетривиальных задач системного администрирования, которые не может "вытянуть" Bash. Наверное переломным для меня моментом была находка библиотеки Paver, которая судя по всему делает все то же, что и библиотека Rake.

На первых этапах разработка велась с использованием IronPython + paver + Visual Studio 2010 + Python Tools for Visual Studio 2010 beta, позже на CPython 2.7 + paver + concurrent.futures + lxml + Eclipse + PyDev. Сейчас присматриваюсь к среде разработки JetBrains PyCharm, хотя использование коммерческого продукта для подобных задач кажется расточительным.

По состоянию на сегодняшний день build/deplyment скрипты представляют из себя сборную солянку следующих библиотек:
- paver, BSD
- concurrent.futures, BSD
- lxml, BSD
- py-restkit, BSD
- simplejson, MIT
- http-parser, MIT

Так же я обращал внимание на следующие библиотеки:
- GitPython, BSD, работает, но не понадобилась
- PyActiveResource, MIT, не подошла из-за функциональных ограничений
- python-rest-client, GPLv3, не пробовал из-за лицензионных ограничений

среда, 17 августа 2011 г.

Цитаты из книги "Программист-прагматик. Путь от подмастерья к мастеру"

На прошлой неделе закончил читать книгу Эндрю Ханта и Дэвида Томаса "Программист-прагматик. Путь от подмастерья к мастеру".

Читал книгу в формате FictionBook на своем смартфоме Samsung Galaxy S с помощью замечательной программы-читалки Cool Reader.

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

Авторы раскрывают скорее принципы, которыми должен руководствоваться любой программист в своей повседневной работе.

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

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

Ниже привожу цитаты из книги, которые мне понравились.


(Об управлении временем жизни у зависимых объектов)

Есть три основных варианта развития событий:
1. Структура верхнего уровня также несет ответственность за освобождение любых входящих в нее подструктур. Затем эти структуры рекурсивно удалят данные, содержащиеся в них, и т. д.
2. Структура верхнего уровня просто освобождается. Любые структуры, на которые она указывает (и на которых нет других ссылок), становятся "осиротевшими".
3. Структура верхнего уровня отказывается освобождать себя, если в нее входят какие-либо подструктуры


Хотя принцип "модель-визуальное представление-контроллер" обычно реализуется в контексте графического интерфейса, на самом деле он является универсальной методикой программирования. Визуальное представление – это некая интерпретация модели (возможно, подмножества), и она не обязана быть графической. Контроллер в большей части является механизмом координации и не должен ассоциироваться с устройством ввода любого типа.

Подсказка 24: Занимайтесь устранением проблемы, а не обвинениями

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

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

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


Многие книги и учебные пособия относят процедуру сбора исходных требований к начальной фазе проекта. Термин «сбор» напоминает о племени счастливых аналитиков, занимающихся собирательством камней-самородков мудрости, разбросанных по земле на фонеприглушенного звучания "Пасторальной симфонии". Этот термин напоминает о том, что все требования уже имеются в наличии, нужно лишь отыскать их, положить в корзину и весело шагать дальше.
Это не совсем так. Требования редко лежат на поверхности. Обычно они находятся глубоко под толщей предположений, неверных представлений и политики.


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

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


Подсказка 59: Дорогие инструменты не всегда создают лучшие решения

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


В группе L Стоффел руководит шестью первоклассными программистами – это руководящая работа, которую можно приравнять к управлению бродячими котами.
Журнал "Washington Post" от 9 июня 1985 г.


Подсказка 66: Дефект должен обнаруживаться единожды

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


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


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

воскресенье, 20 марта 2011 г.

Сегодня Windows 7 довел меня до белого каления

На системном разделе моего ноутбука объем которого составляет 25 Гб (!) нехватает свободного места. Точнее там тупо ноль байт. Из-за этого масса проблем.

В качестве решения проблемы решил перенести c:\Program Files\ и c:\Program Files (x86)\ на другой раздел и сделать Junction Point.

Скопировал все содержимое, но теперь не могу удалить каталог и поменять права, даже с правами администратора.

Администратор не имеет права на удаление этого каталога, только TrustedInstaller и Local System имеет право удалить этот каталог.

Запустить cmd сессию от Local System аккаунта можно с помощью PsTools\PsExec. Но они не работают из-за какого-то дефекта в 64-редакции Windows 7.

Есть хотфик, который решает эту проблему, но чтобы его скачать нужно ввести свой email и капчу. Терпение на исходе.

На почтовый ящик пришло письмо с прямой ссылкой и паролем. Скачал, распаковал. После чего получил сообщение что hotfix не может быть установлен.



Если посмотреть на проблему изначально - 25 Гб под системный раздел для ноутбука это много или мало по состоянию на 2011 год?

Чтобы картина была более ясна отмечу что в каталог c:\Users я не сохраняю никаких своих документов.

Весь установленный софт занимает не так уж и много места:

305 Мб - C:\Program Files\
441 Мб - C:\Program Files (x86)\
1,31 Гб - C:\soft\ причем этот каталог можно не считать, так как все данные лежат на другом разделе, а это Junction Point.


Иногда я жалею что я .NET разработчик, который зависит от операционной системы Windows.

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


Update: Удалось найти достаточно простое решение моей проблемы вот тут
http://helpdeskgeek.com/windows-7/windows-7-how-to-delete-files-protected-by-trustedinstaller/