Archiv der Kategorie: Projects

Kausale Ketten: Gründe für das Scheitern von Softwareprojekten

Auf dieser Github Page habe ich begonnen die Problemen in Softwareprojekten, die uns täglich begegnen, genauer zu analysieren, um diese vermeiden bzw. beheben zu können.

In Gesprächen mit Teilnehmern meiner Workshops werden mir regelmäßig Symptome geschildert, von denen ich der Meinung bin, dass die Ursache wie so oft tiefer liegt.

Tituliert habe ich das als kausale Ketten und orientiere mich dabei an folgendem Schema:

  • Was ist das wahrgenommene Problem, also das Symptom
  • Wie kam es dazu, also die Verlaufsbetrachtung
  • Warum kam es dazu, was ist die Ursache

Zudem suche ich nach nachvollziehbaren Beispielen, um die Theorie zu untermauern.

Ich verstehe die Seite als Anreiz zum Nachdenken. Alle „Thesen“ sollen als Einstieg in eine lebendige Diskussion dienen.

Gebt mir gerne Feedback in Form von Pull Requests.

Schneller zur besseren Webseite mit einer Business Analyse

Für unseren größten Kunden die heco gmbh haben wir eine Business Analyse durchgeführt, um vor der anstehenden Neuentwicklung die richtigen Entscheidungen treffen zu können. Wie wichtig die Informationen über Markt, Mitbewerber, Anwender und Kunden sind und welche Schlussfolgerungen sich daraus ziehen lassen, zeige ich in diesem Video:

Während dies bei „normalen“ (im Sinne von offline bzw. intern) Software-Projekten schon eher Usus ist, kann man davon bei Web-Projekten  noch lange nicht sprechen. Meiner Erfahrung nach spielen hier Aussehen und „etwas auf die Leinwand bringen“ die erste Geige. Eine schier riesige Zahl an Feature-Wünschen, die man auf anderen Seiten gesehen hat, lässt die Stakeholder träumen.
 

Der Langsamste, der sein Ziel nicht aus den Augen verliert, geht noch immer geschwinder, als jener, der ohne Ziel umherirrt. – Lessing

 

Aus unserer Sicht ist es elementar sich die Zeit zu nehmen, um sich als Dienstleister in die Domäne des Kunden einzuarbeiten. Denn viele hoch priorisierte Wünsche sind eher nice-to-have Anforderungen. Oder schlimmer: Das „Warum“ ist gar nicht geklärt und das Produkt bzw. die Webseite wird am eigentlichen Zweck vorbei entwickelt. Da wird dann auch mal der Wunsch nach einem News-System laut, obwohl im Jahr nur 1-2 Neuigkeiten auf der Webseite eingepflegt werden sollen. Dabei wäre es unter Umständen geradezu eine Revolution in der Branche, würde man im Gegensatz zu den Mitbewerbern Echtzeitbestände und -preise auf der Webseite anbieten.

Um richtig priorisieren und die Technologie auswählen zu können, ist deshalb ein solides Know How der Domäne des Kunden notwendig, denn sonst verbaut man sich schnell Möglichkeiten, die dann teuer und langwierig umgebaut werden müssen. Setze ich beispielsweise auf SignalR, so kann ich jedem Besucher, der ein bestimmtes Produkt geöffnet hat, Änderungen am Bestand aktiv mitteilen. Auf der anderen Seite muss ich mir nicht unnötig Komplexität ins Projekt holen, wenn der Bestand von vor 2 Stunden völlig ausreichend ist. Oder wenn der Besucher gar nicht in der Lage ist bestimmt Funktionen zu bedienen (ja, das gibt es durchaus => kenne deine Anwender).

Viel Spaß mit dem Video, auch wenn es etwas lang wurde. Daran merkt man aber schon wie weitreichend das Thema ist. Schreibt mir in die Kommentare, ob ihr auch schon zu solchen Projekten gestoßen seid, bei denen das fehlende Domänenwissen zu Entwicklungsproblemen führte.

Anwender durch zielgerichtete Release Notes in die Produktentwicklung integrieren

In diesem Video zeige ich eine von vielen maßgeschneiderten Funktionen unseres Release Prozesses. Das Ziel dabei war es die Anwender noch mehr in die Produktentwicklung einzubinden.

Die langfristige Vision: Anwender bringen selbst die Anforderungen ein. Wie das genau gemeint ist und wie wir das umgesetzt haben, seht ihr im Video. Unter anderem kommen YouTrack und TeamCity zum Einsatz.

 

Über ein Rake-Skript, welches auf ein bereits existierendes YouTrack-Gem zurückgreift, sprechen wir die Rest-API an. Vielen Dank an Alexander Groß von GROSSWEBER für die Hilfe bei der Umsetzung unserer Vision.

require 'youtrack'
require 'ostruct'
module Build
class YouTrack
class View
attr_reader :issues, :by_department, :by_relevance
def initialize(issues)
@issues = issues
@by_department = department(issues)
@by_relevance = relevance(issues)
end
private
def department(issues)
create_view(issues, proc { |issue| issue.departments })
end
def relevance(issues)
create_view(issues, proc { |issue| issue.relevant_to })
end
def create_view(issues, group_by)
view = issues.inject({}) do |memo, issue|
group_by.call(issue).each do |rel|
previous_value = memo.fetch(rel, [])
new_value = previous_value << issue
memo[rel] = new_value.sort_by(&:issue_id)
end
memo
end
Hash[view.sort]
end
end
class << self
NOT_FOUND = proc { { 'value' => nil } }
def fixed_issues(from, to)
resource = client.issues
maybe_patch_unreleased_methods(resource)
query = query(from, to)
puts "Getting issue list from YouTrack using query: #{query}"
issues = resource.list(filter: query, max: 1000)
issues = force_list(issues)
transformed = transform(issues)
View.new(transformed)
end
private
def client
@client ||= Youtrack::Client.new do |c|
c.url = 'your-url'
c.login = 'your-user'
c.password = 'your-pw'
c.connect!
end
end
def maybe_patch_unreleased_methods(issues)
return if issues.respond_to?(:list)
warn 'Patching #list method'
# Get a list of issues for a search query.
#
# attributes
# filter string A query to search for issues.
# with string List of fields that should be included in the result.
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default.
# after integer A number of issues to skip before getting a list of issues.
#
def issues.list(attributes = {})
attributes[:max] ||= 10
get("issue?#{URI.encode_www_form(attributes)}")
end
end
def query(from, to)
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}"
end
def excluded_subsystems
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS']
return unless spec
spec = spec.split(',')
return if spec.empty?
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}"
end
def force_list(issues)
issues = issues.to_hash['issueCompacts']['issue'] rescue []
([] << issues).flatten(1)
end
def transform(issues)
issues.map do |x|
field = x['field']
project = field.find { |f| f['name'] == 'projectShortName' }['value']
number = field.find { |f| f['name'] == 'numberInProject' }['value']
issue_id = "#{project}-#{number}"
subsystem = subsystem(field)
summary = field.find { |f| f['name'] == 'summary' }['value']
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value']
departments = arrayify(field, 'Department')
relevant_to = arrayify(field, 'Relevant to')
OpenStruct.new(issue_id: issue_id,
subsystem: subsystem,
summary: summary,
info: info,
departments: departments,
relevant_to: relevant_to)
end
end
def subsystem(field)
value = field.find { |f| f['name'] == 'Subsystem' }['value']
return nil if value == 'No Subsystem'
value
end
def arrayify(field, key)
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value']
return [] if value.nil?
([value]).flatten.sort
end
end
end
end
view raw youtrack.rb hosted with ❤ by GitHub

Wer weitere Einblicke in unseren Prozess bekommen möchte, der kann mir dazu einen Kommentar hinterlassen.

require 'youtrack'
require 'ostruct'
module Build
class YouTrack
class View
attr_reader :issues, :by_department, :by_relevance
def initialize(issues)
@issues = issues
@by_department = department(issues)
@by_relevance = relevance(issues)
end
private
def department(issues)
create_view(issues, proc { |issue| issue.departments })
end
def relevance(issues)
create_view(issues, proc { |issue| issue.relevant_to })
end
def create_view(issues, group_by)
view = issues.inject({}) do |memo, issue|
group_by.call(issue).each do |rel|
previous_value = memo.fetch(rel, [])
new_value = previous_value << issue
memo[rel] = new_value.sort_by(&:issue_id)
end
memo
end
Hash[view.sort]
end
end
class << self
NOT_FOUND = proc { { 'value' => nil } }
def fixed_issues(from, to)
resource = client.issues
maybe_patch_unreleased_methods(resource)
query = query(from, to)
puts "Getting issue list from YouTrack using query: #{query}"
issues = resource.list(filter: query, max: 1000)
issues = force_list(issues)
transformed = transform(issues)
View.new(transformed)
end
private
def client
@client ||= Youtrack::Client.new do |c|
c.url = 'your-url'
c.login = 'your-user'
c.password = 'your-pw'
c.connect!
end
end
def maybe_patch_unreleased_methods(issues)
return if issues.respond_to?(:list)
warn 'Patching #list method'
# Get a list of issues for a search query.
#
# attributes
# filter string A query to search for issues.
# with string List of fields that should be included in the result.
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default.
# after integer A number of issues to skip before getting a list of issues.
#
def issues.list(attributes = {})
attributes[:max] ||= 10
get("issue?#{URI.encode_www_form(attributes)}")
end
end
def query(from, to)
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}"
end
def excluded_subsystems
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS']
return unless spec
spec = spec.split(',')
return if spec.empty?
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}"
end
def force_list(issues)
issues = issues.to_hash['issueCompacts']['issue'] rescue []
([] << issues).flatten(1)
end
def transform(issues)
issues.map do |x|
field = x['field']
project = field.find { |f| f['name'] == 'projectShortName' }['value']
number = field.find { |f| f['name'] == 'numberInProject' }['value']
issue_id = "#{project}#{number}"
subsystem = subsystem(field)
summary = field.find { |f| f['name'] == 'summary' }['value']
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value']
departments = arrayify(field, 'Department')
relevant_to = arrayify(field, 'Relevant to')
OpenStruct.new(issue_id: issue_id,
subsystem: subsystem,
summary: summary,
info: info,
departments: departments,
relevant_to: relevant_to)
end
end
def subsystem(field)
value = field.find { |f| f['name'] == 'Subsystem' }['value']
return nil if value == 'No Subsystem'
value
end
def arrayify(field, key)
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value']
return [] if value.nil?
([value]).flatten.sort
end
end
end
end

view raw
youtrack.rb
hosted with ❤ by GitHub

require 'youtrack'
require 'ostruct'
module Build
class YouTrack
class View
attr_reader :issues, :by_department, :by_relevance
def initialize(issues)
@issues = issues
@by_department = department(issues)
@by_relevance = relevance(issues)
end
private
def department(issues)
create_view(issues, proc { |issue| issue.departments })
end
def relevance(issues)
create_view(issues, proc { |issue| issue.relevant_to })
end
def create_view(issues, group_by)
view = issues.inject({}) do |memo, issue|
group_by.call(issue).each do |rel|
previous_value = memo.fetch(rel, [])
new_value = previous_value << issue
memo[rel] = new_value.sort_by(&:issue_id)
end
memo
end
Hash[view.sort]
end
end
class << self
NOT_FOUND = proc { { 'value' => nil } }
def fixed_issues(from, to)
resource = client.issues
maybe_patch_unreleased_methods(resource)
query = query(from, to)
puts "Getting issue list from YouTrack using query: #{query}"
issues = resource.list(filter: query, max: 1000)
issues = force_list(issues)
transformed = transform(issues)
View.new(transformed)
end
private
def client
@client ||= Youtrack::Client.new do |c|
c.url = 'your-url'
c.login = 'your-user'
c.password = 'your-pw'
c.connect!
end
end
def maybe_patch_unreleased_methods(issues)
return if issues.respond_to?(:list)
warn 'Patching #list method'
# Get a list of issues for a search query.
#
# attributes
# filter string A query to search for issues.
# with string List of fields that should be included in the result.
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default.
# after integer A number of issues to skip before getting a list of issues.
#
def issues.list(attributes = {})
attributes[:max] ||= 10
get("issue?#{URI.encode_www_form(attributes)}")
end
end
def query(from, to)
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}"
end
def excluded_subsystems
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS']
return unless spec
spec = spec.split(',')
return if spec.empty?
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}"
end
def force_list(issues)
issues = issues.to_hash['issueCompacts']['issue'] rescue []
([] << issues).flatten(1)
end
def transform(issues)
issues.map do |x|
field = x['field']
project = field.find { |f| f['name'] == 'projectShortName' }['value']
number = field.find { |f| f['name'] == 'numberInProject' }['value']
issue_id = "#{project}#{number}"
subsystem = subsystem(field)
summary = field.find { |f| f['name'] == 'summary' }['value']
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value']
departments = arrayify(field, 'Department')
relevant_to = arrayify(field, 'Relevant to')
OpenStruct.new(issue_id: issue_id,
subsystem: subsystem,
summary: summary,
info: info,
departments: departments,
relevant_to: relevant_to)
end
end
def subsystem(field)
value = field.find { |f| f['name'] == 'Subsystem' }['value']
return nil if value == 'No Subsystem'
value
end
def arrayify(field, key)
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value']
return [] if value.nil?
([value]).flatten.sort
end
end
end
end

view raw
youtrack.rb
hosted with ❤ by GitHub

Schlanke Prozesse sind gute Prozesse oder warum wir Kanban einführen

In unserer IT Administration führen wir regelmäßige Retrospektiven durch. Das ermöglichte es uns Kategorien von Problem zu identifizieren, wie sie auf dem Bild zu sehen sind.

2015-03-04 18.36.43

  • Weniger wichtige Aufgaben wurden zuerst erledigt (Priorisierung)
  • Vermeintlich kurze Projekte haben sich stark in die Länge gezogen
  • Aufgaben wurden nicht vollständig erledigt
  • Zuständigkeiten / Ansprechpartner waren nicht klar
  • Aktuelle Projektstände waren nicht transparent
  • Aufgaben blieben bei Externen hängen bzw. blockierten uns
  • Engpässe / Verzögerungen machten uns das Leben schwer
  • Planung war schwierig
  • Häufiges “auf einen Stand bringen” und mehrfaches Besprechen gleicher Punkte

Die Systemadministration ist von Natur aus ein stark vom Tagesgeschäft abhängiges Umfeld mit viel Klein-Klein-Arbeit. Der Anwender ruft an, meldet PC Probleme (Spezifisches hört man selten), der Kollege unterbricht seine eigentliche Tätigkeit wie z.B. die Vorbereitung einer größeren Umstellung und kümmert sich um die Nöten des besorgten Anrufers. Kaum zurück und wieder in die Umstellung vertieft, kommt eine E-Mail, dass die Druckertoner leer sind. Erneut gilt es die aktuelle Arbeit zu unterbrechen.

Darüber hinaus laufen natürlich immer Projekte mit anderen Abteilungen oder Externen (Telekommunikationsanbieter, Dienstleister, etc.), die voran getrieben werden müssen. Selbst der Entwickler-Kollege aus der selben Abteilung ruft an und möchte einen neuen Testserver aufgesetzt bekommen – am besten vorgestern versteht sich.

Kurzum: Ein reges Tagesgeschäft parallel zu großen Projekten und das alles im Umfeld von unterschiedlichen Stakeholdern. So wie es auch in Marketingabteilungen oder Zentralen der Fall ist.

Nüchtern betrachtet handelt es sich um eine Warteschlangensystem. Da Menschen nicht gut darin sind viele Dinge parallel zu machen, vermuten wir hierin eine der Ursachen: Zu viele Dinge werden gleichzeitig erledigt. Außerdem stehen wir Dritten skeptisch gegenüber, welche uns rein gefühlsmäßig ausbremsen. Bei den Telekommunikationsanbietern können wir indes von einer Tatsache sprechen…Darüber hinaus meinen wir diverse weitere Hindernisse erkannt zu haben, die den Arbeitsfluss immer wieder stören.

Um weil wir uns gerne verbessern möchten, eine zu große Umstellung auf einmal eher scheuen und der Evidenz der Vermutung den Vortritt geben möchten, haben wir uns entschieden Kanban einzuführen. Auf einen Nenner gebracht liegen dem 3 Prinzipien und 5 Praktiken zu Grunde.

Prinzipien:

  • Starte mit dem, was du jetzt machst
  • Verfolge inkrementelle, evolutionäre Veränderungen
  • Respektiere initiale Prozesse, Rollen, Verantwortlichkeiten und Job-Titel

Praktiken:

  • Mache Arbeit sichtbar
  • Limitiere den WIP
  • Manage den Arbeitsfluss
  • Mach Prozess-Regeln explizit
  • Führe gemeinschaftliche Verbesserungen durch (basierend auf Modellen)

Alle Regeln werden penibel eingehalten, jedoch ist die erste Regel alle Regeln abzuschaffen, die nicht mehr funktionieren.

Über die ersten Ergebnisse werde ich demnächst berichten. Falls jemand schon gute Erfahrungen primär im Administrationsumfeld gemacht hat, dann würde ich mich über Input freuen. Gerne auch aus der Softwareentwicklung.

Aus der Praxis – Wie sich unser Unternehmen durch Social Media verändert hat

Social Media ist nur ein Trend

Ich bin vor einigen Monaten aus unserer Social Media Gruppe (meine früheren Berichte zu unserer Arbeit) ausgetreten. Nicht weil ich darin keinen Sinn mehr gesehen oder weil ich nichts mehr hätte beisteuern können. Vielmehr liegen meine Stärken mehr darin Ideen zu entwickeln und einzuführen, als diese bis zum Schluss zu begleiten. Schluss bedeutet hierbei bis die letzten Kollegen „überzeugt“ wurden. Die Autorinnen Rising Linda und Mary Lynn sprechen in ihrem tollen Buch Fearless Change von sogenannten “Innovators” und “Early Adopters”  (einen Einführungs-Podcast dazu gibt es hier). Mit Laggers werden übrigens Persönlichkeitstypen bezeichnet, die nicht zu überzeugen sind. Dazwischen reihen sich die Early Majority und Late Majority ein.

Nachdem vor kurzem die Gruppe überein kam sich aufzulösen, sprach mich eine Kollegin an. Sie ist der Meinung, dass viele Ideen respektive Ansätze dabei sind, bei denen es sich lohnt diese weiterzuführen. Zum Beispiel unsere Textwerkstätten. Sie wollte wissen, wie ich über die Entscheidung denke und ob ich einen Vorschlag hätte, um die Gruppe neu auszurichten. Vor allem da sich die bisherigen Ergebnisse sehen lassen konnten und sie die Art der interdisziplinären Kollaboration im Vergleich zur (homogenen) Abteilungsarbeit spannend fand.

Nun wird der ein oder andere Leser vermutlich denken: Das war doch klar. Social Media und Unternehmen passen nicht zusammen. Das ist ein vorübergehender Trend, den jeder früher oder später abhaken muss!

Halte an deinen Zielen fest, bleibe dabei aber flexibel

Mein Sicht darauf war dann doch eine andere, was ich meiner Kollegin wie folgt erklärt habe: Das Ende der Social Media Gruppe ist lediglich dem Namen und der Beschränkung auf eine festgelegte Personengruppe geschuldet. Die Idee dahinter hat längst bei uns Wurzeln geschlagen. Denn inzwischen machen wir unternehmensweite Open Spaces, bei denen die “Lehrer” und “Schüler” Rollen kontinuierlich wechseln und die Agenda von allen Mitarbeitern mitbestimmt wird. Am Vormittag erzählt noch der Kollege aus Berlin umfassend über ein neues Produkt und die Kollegin aus Nürnberg hört zu, am Nachmittag werden die Rollen getauscht und die Kollegin referiert über Werkstoffe und deren Einsatzszenarien.

Die Meetings unserer Marketing- & Einkaufs-Abteilung sind inzwischen für alle geöffnet. Durch gezielte Einladungen abteilungsfremder Kollegen soll das gefördert werden. Themen- und Projektvorschläge werden in den Wochen zuvor von allen Mitarbeitern im Wiki zusammengetragen. Jedem „Stakeholder“ ist freigestellt Input zu liefern.

Ein weiteres Beispiel ist unser Arbeitsplatztausch, bei dem jeder Mitarbeiter mindestens 1x jährlich in eine andere Abteilung wechselt, um über den eigenen Tellerrand hinaus zu schauen. Und natürlich schreibt der Kollege seinen Erfahrungsbericht in unseren Corporate Blog. Stephan ist übrigens kein Mitglied der Social Media Gruppe! Damit ist er einer von vielen, die bereits gebloggt haben.

Mir fallen auf Anhieb noch viele weitere Ausprägungen ein, doch unser Erfolg zeigt sich meiner Meinung nach am besten darin, dass ich kürzlich beim Vorbeigehen nicht umhin kam zu hören, wie der Einkaufsleiter in Nöttingen am Telefon einer Kollegin aus Frankfurt wie selbstverständlich mitteilte: “Das kannst du alles im Wiki nachlesen und dann ggf. ergänzen”. Dass der Einkaufsleiter noch ein paar Wochen zuvor seine Mitarbeiterin – selbige ist Teil der Social Media Gruppe und daher bestens geschult im Umgang mit dem Wiki – gefragt hat wie der Inhalt einzupflegen ist, brachte mich dann doch zum Schmunzeln. Mit einem Video zur Integration der Wiki-Inhalte in unser Warenwirtschaftssystem habe ich die Vernetzung bei uns bereits illustriert.

Neben einigen weiteren Beispielen wies ich meine Kollegin noch darauf hin, dass sie selbst erst kürzlich gegenüber dem Vorgesetzten den Wunsch geäußert hat in ihrer Abteilung regelmäßige Retrospektiven zur Verbesserung der Prozesse umzusetzen. Ebenfalls ein Ansatz, den wir bei der Social Media Gruppe einsetzten.

tree-200795_1280

Quelle: http://pixabay.com/de/baum-struktur-netzwerke-internet-200795/

Fazit:

Nur diejenigen, die Social Media nicht auf Werkzeuge wie Facebook und Twitter reduzieren, sondern die Ansätze und Gedanken dahinter verstehen, können das der Sache inhärente Potential nutzen. Die Schulungs-Agenda sollte eben nicht mehr nur vom Abteilungsleiter definiert oder Geschäftsprozesse durch die Geschäftsleitung festgelegt werden. Stattdessen muss sich eine Kultur etablieren, bei der die Fähigkeiten aller genutzt werden und das Wissen der Masse kontinuierlich geteilt wird. Dadurch entsteht eine Lernkultur, bei der Informationen nach Belieben beigesteuert und herausgezogen werden können. Social Media ist insofern auch keine neue Idee, jedoch sind inzwischen viele digitale – teils populäre, teils weniger bekannte – Werkzeuge auf dem Markt. Mit dem inzwischen signifikanten und noch weiter steigenden Anteil der sogenannten Wissensarbeit ist nicht mehr die Frage, ob man auf den Zug aufspringen sollte. Die Herausforderung liegt darin die passenden Werkzeuge zu evaluieren und eine entsprechende Unternehmenskultur zu etablieren. Denn wer nicht mit der Zeit geht, geht mit der Zeit.

Wie stehts bei euch um Change Management, Social Media und Innovationen? Welche Werkzeuge nutzt ihr? Seid ihr schon auf einem guten Weg oder springt ihr gar nicht auf den Zug auf?

PostSharp reports broken references when using Paket

If you use Paket instead of NuGet (why? therefore!) you will experience the following difficulties:

imageGiven is the following Solution/Project. It contains a paket.references file containin just one line “PostSharp”, which is

The PostSharp Assembly is added when you your “paket.references” file in the project directory containing just one line “PostSharp”. If you execute “paket install” in your command promt, paket adds PostSharp as reference to the project and modifies the csproj accordingly.

The next time you run a build, PostSharp comes up with this dialog:

postsharp

Since the VisualStudio PostSharp plugin detects a reference to PostSharp.dll, it expects a packages.config which it can’t find. If you don’t agree, the solution builds successfully but the code won’t be modified by PostSharp.

imageIf you agree, PostSharps adds a packages.config and replaces the existing PostSharp reference with the newest stable one (which probably is not the version you have specified in Paket!).

I posted this on Twitter. The Paket maintainers can’t handle this and PostSharp won’t change this behaviour.

image

image

image

So by now, I don’t see any solution except that Paket users can ask PostSharp to change this behaviour.

//Edit: Have a look at the comments. The unsolved problem is that you have to execute the „install.ps1“. Paket doesn’t do that but NuGet will also remove this feature in a future release.

Tipps zum Customer Relationship Management in kleinen und mittleren Unternehmen

Mit folgendem Webcast möchte ich kleine und mittlere Unternehmen (KMU) einen groben Überblick geben, was es bei der Auswahl von Customer Relationship Management (CRM) Systemen zu beachten gilt. In 20 Minuten mache ich eine kurze Analyse und erkläre den unterschied von funktionalen und nicht-funktionalen Anforderungen. Dabei ist es wichtig zu wissen, dass die nicht-funktionalen zwar nicht sichtbar sind, aber durchaus teuer werden können. Am Schluss nenne ich dann einige Produkte, die sich primär in interne und externe Lösungen (Stichwort Cloud) kategorisieren lassen. Bei der Auswahl wird dann der Rückschluss auf die Anforderungsanalyse gezogen.

 

Hier geht es zum Video. Wer sich fragt, welches Programm ich zur Aufbereitung verwende. Es handelt sich um den MindManager der Firma MindJet.

CRM

Hier sind noch weiterführende Informationen:

Aus der täglichen Retrospektive #3

2014-07-31 12.08.30Unsere Retrospektiven gehen weiter und die Teammitglieder entwickeln meiner Einschätzung nach ein immer besseres Gespür für Impediments. Das ist für mich als Scrum Master besonders erfreulich. Ich habe mir wieder einmal etwas herausgegriffen, von dem ich denke, dass es dem Leser helfen kann.

Es hat sich herausgestellt, dass ein wichtiger Prozess bisher nicht explizit gemacht wurde: Der Go Live.

Das führte unter anderem dazu, dass das Update erst mit mehreren Tagen Verzögerung und teilweise sogar mit Änderungen aus dem neuen Sprint durchgeführt wurde. Darüber hinaus war nicht gewährleistet, dass mit der Codebasis auch die Datenbasis aktualisiert wurde. Deshalb haben wir uns zusammengesetzt, den Prozess besprochen und diesen im Wiki festgehalten.

image

Darin wird klar zw. Daten- und Code- Go Live unterschieden. Damit einhergehen auch unterschiedliche Termine und Verantwortliche. Interessanterweise wurde in der Diskussion deutlich, dass der Prozess noch detailliert visualisiert und kommuniziert werden muss. In Kanban ist das fest als Regel aufgelistet:

                  Visualize Your Workflow

Einen Einblick in unser Wiki und den Prozess werde ich demnächst in Form eines Screencasts auf YouTube und auf meinen Blog stellen. Als Abonnent kriegt ihr das automatisch mit.

Mit welchem Tool verfasst bzw. persistiert ihr eure Regeln? Wer macht das? Wird das kontinuierlich erweitert oder war das nicht mehr notwendig? Ist es euch auch schon öfters passiert, dass ihr feststellen musstet, dass Regeln nicht explizit gemacht und dadurch Probleme verursacht wurden? Gerne könnt ihr mir einen Kommentar schreiben.

Testen von Datenbankabfragen – Es geht auch einfach

In diesem Screencast zeige ich wie selbst Abfragen mit mehreren Bedingungen und Sortierungen einfach getestet werden können. Unter der Haube kommt das Entity Framework zum Einsatz. Davon kriegt man dank Dependency Injection aber nichts mit.

Die erwähnte Webcast Serie, sowie weiteren Beispielcode und das Projekt auf Github gibt es hier. Feedback oder Fragen nehme ich gerne entgegen. Wenn ihr wollt, dass ich die Serie fortsetze, sprecht mich über einen Kanal eurer Wahl an.

Flexibles Bootstrapping durch Composition

In diesem Webcast zeige ich unseren Kompositions-Ansatz für das Bootstrapping. Die Interfaces sind schmal, die Implementierungen überschaubar, das Ganze ist flexibel und lässt sich gut testen.

Hier der gezeigte Code:

   1: public class BootstrapperContext

   2: {

   3:     public BootstrapperContext()

   4:     {

   5:         AppConfig = new ApplicationConfig();

   6:         Container = new WindsorContainer();

   7:     }

   8:  

   9:     public IWindsorContainer Container { get; private set; }

  10:     public ApplicationConfig AppConfig { get; private set; }

  11: }

   1: public interface IAmABootstrapperAction

   2: {

   3:     void Execute(BootstrapperContext context);

   4: }

 

   1: public interface IAmABootstrapperComposition

   2: {

   3:     IEnumerable<IAmABootstrapperAction> Actions { get; }

   4: }

 

   1: public class BootstrapperExecutor

   2: {

   3:     public static void StartupApplication(IAmABootstrapperComposition bootstrapperComposition)

   4:     {

   5:         var exceptionMessage = "Beim Starten der Anwendung ist ein Fehler aufgetreten. Bitte den Support kontaktieren.\n\n";

   6:  

   7:         if (bootstrapperComposition.Actions == null || !bootstrapperComposition.Actions.Any())

   8:         {

   9:             throw new BootstrapperException(exceptionMessage, new ArgumentOutOfRangeException("Auf dem Bootstrapper waren keine Actions definiert"));

  10:         }

  11:  

  12:         var context = new BootstrapperContext();

  13:  

  14:         var time = TimedAction.Run(() =>

  15:                                    {

  16:                                        foreach (var action in bootstrapperComposition.Actions)

  17:                                        {

  18:                                            var actionName = action.GetType().Name;

  19:                                            SiAuto.Main.LogMessage(string.Format("{0} gestartet", actionName));

  20:  

  21:                                            var timeTaken = TimedAction.Run(() => { ExecuteAction(action, context, exceptionMessage); });

  22:  

  23:                                            SiAuto.Main.LogMessage(string.Format("{0} in {1} erfolgreich durchgeführt",

  24:                                                                                 actionName,

  25:                                                                                 timeTaken.Format())

  26:                                                );

  27:                                        }

  28:                                    });

  29:     }

  30:  

  31:     private static void ExecuteAction(IAmABootstrapperAction action, BootstrapperContext context, string exceptionMessage)

  32:     {

  33:         try

  34:         {

  35:             action.Execute(context);

  36:         }

  37:         catch (Exception ex)

  38:         {

  39:             SiAuto.Main.LogException(string.Format("Fehler beim Bootstrapping: {0}", ex.GetFullExceptionMessage()), ex);

  40:             throw new BootstrapperException(string.Format("{0}{1}", exceptionMessage, ex.GetFullExceptionMessage()), ex);

  41:         }

  42:     }

  43: }

 

Welchen Ansatz verfolgt ihr?

Fragen oder Feedback könnt ihr mir gerne als Kommentar hinterlassen.

Aus der täglichen Retrospektive #2

Aufgrund von unterschiedlichen Browserversionen und Extensions oder wegen gecachten Inhalten kam es bei der Abnahme von User Stories bei uns häufig zu Problemen. Beispielsweise wurden alte Styles oder falsche Bilder geladen. Eine weitere Störquelle waren Fehler in Fremdsprachen oder in besonderen Daten-Konstellationen (sehr lange Breadcrumb, tiefe Hierarchien).

Daraus zogen wir zwei Konsequenzen:

  • Alle Tester und der PO bekamen die PortableApps.com Plattform, in welcher Firefox und Chrome mit einheitlichen Extensions und Konfigurationen installiert sind. Zwei Konfigurationen bewirken unter anderem, dass nichts gecacht wird und nach dem Schließen des Browsers alle Daten entfernt werden. Außerdem ist das NoTracking-Kennzeichen aktiviert. Per copy & paste kann ein weiterer Tester schnell “die Testumgebung” erhalten.
  • Darüber hinaus legten wir für besagte Konstellationen Lesezeichen in den Leisten an und dokumentierten den Soll-Stand. Im Unternehmenswiki ist der formale Abnahmeablauf festgehalten.

Certified Scrum Product Owner Schulung

Im Zeitraum vom 18. bis 20. Juni nach ich an einer Certified Scrum Product Owner Schulung bei den Trainern Dr. Jürgen Hoffmann und Peter Beck teil. Den Kurs hatte über die wibas GmbH gebucht. Als Veranstaltungsort wählte ich die NTT DATA Ettlingen Academy.

 

Location

Das Angebot an kostenfreien Parkplätzen war optimal, sodass unnötig langes Suchen am frühen Morgen ausblieb. Die verfügbaren Räume waren schön hell mit genügend Platz für die knapp 20 Kursteilnehmer. Leider mangelte es an einer Klimaanlage, was mir bei tropischen 35°C besonders negativ auffiel. In den kleinen Pausen konnte man sich mit frischem Obst und Gebäck stärken; zur Mittagszeit gab es Essen in der Kantine, welche einen schönen Balkon mit Teich zur geistigen Erholung bot.  Bedenkt man die kurze Strecke für Karlsruher war es geradezu ideal..

 

Die Trainer

Eine kurze Recherche ergab, dass es sich um zwei etablierte, langjährige Trainer handelte, die in der Community recht arriviert sind. Deshalb bekamen die Teilnehmer das, was sie vermutlich am meisten hören wollten: Praxisberichte. Die Frage “Wie habt ihr das bei euch gemacht” kam so ziemlich jedem über die Lippen. Hierfür gibt es von mir “volle Punktzahl”. Die Aussagen waren klar, die didaktischen Methoden variierten in einem gesunden Maße und durch die Backlogs der einzelnen Gruppen konnte jeder in die Themenfokussierung eingreifen. Negativ fielen mir folgende Dinge auf:

Aufgrund meiner T-Shirt Wahl, welche meine Entwicklerwurzeln offen zeigte, wurde wohl der Eindruck bei den Trainern perpetuiert, dass man mich auch als solchen “coachen” sollte. Zumindest empfand ich dies teilweise so bei Gesprächen. Als ich mich zur Aussage, dass es gängige Praxis sei die Entwickler die Product Backlog Items schreiben zu lassen, kritisch äußerte und darüber diskutieren wollte, wurde dies einfach abgetan. Inzwischen habe ich vermehrt in der Community, unter anderem am .NET Open Space, das Thema aufgeworfen. Mein Resümee: Gängige Praxis sieht anders aus. Allerdings möchte ich fairerweise an dieser Stelle anmerken, dass selbst in der Fachliteratur (u.a. im Werk von Boris Gloger) ähnliche Aussagen stehen. Mir geht es jedoch darum, dass ich mich eine Schublade gesteckt sah, sodass eine konstruktive Diskussion in diesem konkreten Fall nicht zu Stande kam.

Ein weiterer Punkt waren ein paar wenige (!) fragwürdige Aussagen. Eine davon implizierte, dass die Definition of Done immanent unvollständig sein müsse, wenn das Produkt keine 100%ige Evolvierbarkeit aufweise.

Rein subjektiv gefielen mir die ausgewählten Planspiele nicht. Das lag vielleicht daran, dass es sich nicht um Geschäftsprozesse handelte. Vielleicht auch an den konkret gewählten Szenarien, wie das Bauen eines Lego-Parks für Kinder. Bei der zu Beginn gewählten Teambildungsmaßnahme mussten Seesterne gelegt werden. Eine gängige Praxis, wie ich von Bekannten mit selbiger Zertifizierung erfahren habe. Allerdings wurde es so umgesetzt, dass die Teams bereits blind in den Raum kommen, die Materialien finden und Position begeben mussten. Wenn aber die erste Gruppe direkt am Eingang stehen bleibt, dann ist das Resultat, dass mehrere Teams vorerst einfach rumstehen müssen, bis der erste Durchgang beendet wurde. Der Lerneffekt ist für mich genauso gegeben, wenn die Teams bereits im Raum mit den Materialien platziert wären. Sehr positiv wiederum kann ich über das Fallbeispiel zur Schätzung in Story Points im warmen Garten berichten. Die Teams sollten sich hierbei auf die Komplexität zum Entfernen von Gegenständen für den bevorstehenden Besuch von Obama (der an diesem Tag tatsächlich in Berlin war) einigen. Das fing bei Aschenbechern an, ging über Tische und hörte bei Autos auf.

 

Zur Schulung

Als einziges Angebot, welches ich in der näheren Umgebung finden konnte, ging dieser Kurs über 3 Tage. Die üblichen Schulungen sind in der Regel auf 2 Tage ausgelegt. Planspiele sind aber relativ zeitaufwendig und der Wunsch nach Erfahrungsberichten der Trainer führt zu starken Verzögerungen. Deshalb kann ich nur jedem empfehlen 3-tägige Seminare zu bevorzugen.

Die Kosten lagen auf gängigem Marktniveau. Bei entsprechendem Vorlauf wird Frühbucherrabatt gewährt.

Als Schulungszeiten waren 10-18 Uhr am ersten Tag und 9-17 Uhr an den darauffolgenden Tagen geplant. Diese wurden recht gut eingehalten, was nach meiner Erfahrung bei Schulungen sonst nur selten funktioniert.

Bereits vor Kursbeginn erhielt ich eine E-Mail mit Vorschlägen zur Vorbereitung. Während der Schulung wurde ein eigens dafür eingerichteter DropBox Ordner kontinuierlich aktualisiert. Darin landeten neben kostenlosen Ebooks die Fotoaufnahmen der erstellten Materialien (quasi eine Timeline der Flip Chart Blätter), sowie diverse Vorlagen und weiterführende Materialien. Ein dediziertes Dokument mit den erarbeiteten Inhalten gab es nicht.

Das klar kommunizierte Ziel war es jeden in die Lage zu versetzen, entscheiden zu können, ob Scrum mit seinen Prinzipien und Werten für das eigene Unternehmen geeignet ist und sich etablieren lässt. Der Fokus lag natürlich auf der Rolle des Product Owners, ist aber auch nach Aussagen der Trainer mit der des Scrum Master zu 80-90% deckungsgleich. Die restlichen 10-20% wurden dann aber speziell hervorgehoben bzw. ggf. auch intensiver erörtert.

Wer viele Planspiele, viel Eigenarbeit, viel Stehen und wenig PowerPoint Slides bevorzugt, kommt definitiv auf seine Kosten. Mir persönlich hätte ein wenig mehr “klassische” Schulung mit theorielastigerem Vorgehen besser zugesagt. Nach meinem Gefühl kommen dadurch mehr Fragen auf, v.a. abseits der der trivialen Oberflächenthemen. Das bestätigte sich für mich im Nachhinein beim Lesen der oben genannten Lektüre von Boris Gloger wieder. (Wenn Folien übrigens schlecht sind, dann weil der Autor Fehler gemacht hat, nicht weil diese per sé schlecht sein müssen, siehe meine Buchempfehlung.) Allerdings verhält es sich so, dass ich nach einer theorielastigen Sitzung der Einzige war,  der diese positiv bewertete. Deswegen gilt: Die Schulung muss nach der eigenen Präferenz ausgewählt werden.

 

Fazit

Auf einer Skala von 1 bis 10 gibt es von mir eine 7 mit dem klaren Hinweis, dass ich die Schulung für Menschen mit anderem Lernmodus durchaus bei 9-10 sehen würde. Darüber hinaus halte ich eine derartige Schulung für ganze Scrum Teams durchaus für förderlich, speziell wenn sich Fallbeispiele und Themen mitbestimmen lassen. Es bleibt ein positiver Rückblick mit kleinen Makeln und dem Ratschlag, dass auch das Seminar das Lesen entsprechender Fachliteratur nicht ersetzt (vice versa!).

Entity Framework Webcasts – Testing

Im siebten Teil meiner Entity Framework Webcast Serie widme ich mich dem Thema Testing. Zunächst nehmen wir uns die fertigen Queries vor, welche sich auch gut ohne Datenbank testen lassen. Schließlich will nicht jeder zum Testen einer Abfrage immer eine entsprechende Datenbank hochfahren und die Daten dafür erzeugen.

Danach stellen wir in einem Mini-Business-Pseudo-Layer ein Szenario nach, welches uns dedizierte Daten für die weitere Verarbeitung liefert, sodass das Testen der Geschäftslogik ebenfalls ohne Datenbank erfolgen kann.

Als BDD Framework für die Unit Tests verwende ich Machine.Specifications und als Mocking Framework kommt FakeItEasy zum Einsatz.

Hier noch die zwei Code Beispiele:

 

Beispiel 1: Testen der Query

   1: [Subject(typeof(GetAddressByCity))]

   2: public class When_addresses_contains_exactly_one_matching_entry

   3: {

   4:     static GetAddressByCity Sut;

   5:     static List<Address> Addresses;

   6:     static Address Actual;

   7:     static Address TestCity;

   8:  

   9:     Establish context = () =>

  10:     {

  11:         TestCity = new Address {City = "test city"};

  12:         Addresses = new List<Address>

  13:         {

  14:             new Address {City = "Karlsruhe"},

  15:             TestCity

  16:         };

  17:         Sut = new GetAddressByCity("test city");

  18:     };

  19:  

  20:     Because of = () =>

  21:     {

  22:         Actual = Sut.Execute(Addresses.AsQueryable());

  23:     };

  24:  

  25:     It should_return_exactly_this_address = () => 

  26:             Actual.ShouldEqual(TestCity);

  27: }

 

Beispiel 2: Testen von Geschäftslogik

   1: class EmployeeBusinessLogicSpecs

   2: {

   3:     [Subject(typeof(EmployeeBusinessLogic))]

   4:     class When_hire_date_is_unknown

   5:     {

   6:         static IUnitOfWork Uow;

   7:         static EmployeeBusinessLogic Sut;

   8:         static Employee DummyEmployee;

   9:  

  10:         Establish context = () =>

  11:         {

  12:             DummyEmployee = new Employee {EmployeeID = 1, 

  13:                     FirstName = "Uli", LastName = "Armbruster"};

  14:  

  15:             Uow = A.Fake<IUnitOfWork>();

  16:  

  17:             A

  18:                 .CallTo(() => Uow.ExecuteQuery(

  19:                     A<GetEmployeeById>

  20:                     .That

  21:                     .Matches(q => q.EmployeeId == DummyEmployee.EmployeeID)

  22:                                   ))

  23:                 .Returns(DummyEmployee);

  24:  

  25:             Sut = new EmployeeBusinessLogic(Uow);

  26:         };

  27:  

  28:         Because of = () => Sut.EnsureValidHireDate(DummyEmployee.EmployeeID);

  29:  

  30:         It should_update_it = () => DummyEmployee.HireDate.ShouldNotBeNull();

  31:  

  32:         It should_save_the_changed_hire_date = () => A

  33:             .CallTo(() => Uow.Commit())

  34:             .MustHaveHappened(Repeated.Exactly.Once);

  35:     }

  36: }

 

 

Weitere Quellen:

Entity Framework Webcasts – Stored Procedures und SQL Queries

Im sechsten Teil meiner Entity Framework Webcast Serie zeige ich, wie die IUnitOfWork erweitert werden muss, um SQL Abfragen und Befehle auszuführen. Im Fokus stehen Value Functions und Stored Procedures.

 

 

Dafür stellt das Entity Framework 2 Schnittstellen bereit:

Ein wichtiger Hinweis an dieser Stelle, welcher eingehender in meinem Webcast erwähnt wird: Zum Ausführen einer SQL Query existieren 2 Implementierungen:

Der wesentliche Unterschied besteht darin, dass im ersten Fall die zurückgegebenen Entitäten nicht getrackt werden, im zweiten hingegen schon. Das ist leicht nachvollziehbar, so muss bei DbContext.Database.SqlQuery ein generischer Parameter, der den Rückgabewert darstellt, übergeben werden. Im Falle von DbSet.SqlQuery ist dies nicht nötig, schließlich befindet man sich zum Zeitpunkt der Abfrage auf einem konkreten Set an Entitäten, die über den Context aufgelöst wurden.

Meine vorgestellte Lösung geht den gleichen Weg wie die in Teil 3 beschriebenen Queries, d.h. es werden quasi SQL Query und Command Repositories angelegt, um einer Streuung innerhalb der Anwendungslandschaft entgegenzuwirken.

Schließlich noch zwei Sicherheitshinweise:

  • Die SQL Befehle werden mit den Berechtigungen des Contexts bzw. des darunterliegenden Sicherheitstoken ausgeführt
  • Beim Erstellen des SQL Strings sollte aus Sicherheitsgründen gegenüber Injections mit String.Format() oder alternativ mit SqlParameters gearbeitet werden.

 

I wrote this blog post as an answer of a question from the Netherlands, so I want to complete this article with a quote:

“When you execute a SqlQuery from Database, the results are never tracked by the context, even if the query returns types that are in the model an known by the context. If you do not want the results to be changed-tracked, use DbContext.Databae.SqlQuery.

Results of a DbSet.SqlQuery will be tracked by the context. Ensuring that results are change-tracked is the primary reason you would choose to use DbSet.SqlQuery over Database.SqlQuery.” (Programming Entity Framework, Page 226)

 

Weitere Quellen:

Entity Framework Webcasts Serie

In den vergangenen Tagen gingen Teil 4 und 5 meiner Entity Framework Webcast Serie online. Außerdem ist geplant in den kommenden Tagen noch Teil 6 zu veröffentlichen. Das liegt darin begründet, dass ich verstärkt per E-Mail adressiert mit der Bitte wurde, die restlichen Teile nachzuschieben. Außerdem erhielt ich mehrfach Anfragen bzgl. diverser Schwerpunkte, die ich in zukünftigen Beiträgen aufgreifen möge. Den Bitten werde ich so weit mir möglich nachkommen.

Deshalb an dieser Stelle nochmal der Hinweis, dass man mich über die üblichen Kommunikationskanäle gerne ansprechen darf. Das ein oder andere Feedback hilft mir auch dabei mich immer wieder aufzurappeln und in der Freizeit derartigen Inhalt zu produzieren.

 

Hier ein kurzer Auszug aus einer Mail, die ich gerade erst vergangene Woche erhalten habe:

Hallo Herr Armbruster,

erstmal kompliment für die 3 m.E. sehr guten Videos. Gerne hätte ich noch mehr bzgl. Entity Framework gesehen. Die Art und Weise wie die Thematik erläutert wird, ist Ihnen sehr gut gelungen.

Schön wäre z.B. noch:

– Genaue Erläuterung der Konfigurationsdatei

– WCF in Verbindung mit EF

– Architekturgestaltung bis hin zur UI

Die Gestaltung und Auslagerung der Queries im Teil3 ist für mich ein riesen Lernfortschritt gewesen. Probleme habe ich jetzt bekommen, als ich die Abfrage, also eine IQuery über einen WCF Dienst laufen lassen wollte. Dieser akzeptiert ja soweit mir bekannt ist, keine generischen Typen.

Können Sie hierzu eine kurze Info geben, wie dies realisiert werden kann?

 

 

Aktuell stehen folgende 5 Webcasts in Full HD Auflösung bereit:

  • Teil 1 – DbContext und POCOS: Wir wechselt man auf den neuen DbContext des Entity Framework 5 und erzeugt sich POCO Objekte frei von jeglichen Technologie-spezifischen Referenzen.
  • Teil 2 – UnitOfWork und IoC: Lose Kopplung ist in diesem Beitrag das Ziel. Der Client verliert vollständig seine Abhängigkeit zum Entity Framework bzw. zur Persistenzschicht.
  • Teil 3 – Queries: Hier zeige ich wie sich ein Query Repository aufbauen und Redundanz vermeiden lässt.
  • Teil 4 – Eigener Kontext: Dieses Video erläutert die Implementierung eines eigenen Kontexts.
  • Teil 5 – Weitere Datenbanken: Der nächste wichtiger Schritt ist das Einbinden weiterer Datenbanken. Der Vorzug hierbei ist die implizite Auflösung des entsprechenden Modells auf Basis von Konventionen.
  • Teil 6 – Stored Procedures und SQL Queries: In Anlehnung an Teil 3 baue ich hier ein entsprechendes Repository für reine SQL Abfragen und Stored Procedures auf.

Der vollständige Quellcode steht in dem Repository ‘MyCleanEntityFramework’ in meinem Github Account zur Verfügung.

Zusammenarbeit mit der Fachabteilung – BDD und TDD helfen

Test Driven Development (TDD) steht dafür, dass Tests vor der eigentlichen Implementierung zu schreiben sind. Es zwingt den Entwickler sich vorher genaue Gedanken über die Architektur und die Implementierung zu machen, sodass “einfaches loslaufen” und ggf. damit einhergehendes “falschlaufen” vermieden wird. Saubere Codequalität wird gefördert.

Behaviour Driven Development bringt nun einen weiteren Ansatz mit ins Spiel: Code soll ergebnisorientiert getrieben sein, d.h. die fachlichen Spezifikationen stehen im Vordergrund. Entsprechende BDD Frameworks wie Machine.Specifications (Link, gibt es auch auf NuGet) unterstützen nun dabei, dieses Ziel umzusetzen.

Damit sich auch Nicht-IT-ler und weniger Test-affine Entwickler etwas darunter vorstellen können, hier ein kleines Beispiel aus der Praxis. Wir praktizieren dies bereits seit einigen Monaten in der Form mit unseren Fachabteilungen:

Aufgabenstellung: Mehrere Produktartikel sind in einer gemeinsamen Bestellmappe zusammengefasst. Diese Mappe besitzt die Kopfdaten ‘Lieferzeit’ (LZ), ‘Bearbeitungszeit’ (BZ), ‘Bestellintervall’ (BI) und ‘nächster Mappentermin’ (MT). Der nächste Mappentermin besagt, wann die Mappe sich beim zuständigen Benutzer melden, sodass dieser eine neue Bestellung auslöst. Das Bestellintervall gibt an in welchen Abständen die Mappe neu bearbeitet werden muss, z.B. alle 3 Wochen. Die restlichen Begriffe sprechen für sich. Nun wird von der Fachabteilung gefordert, dass sogenannte ‘fiktiven Wareneingänge’ (fWE) visuell dargestellt werden (siehe Screenshot). Dabei handelt es sich um Eingänge, die rein rechnerisch in der Zukunft eingehen würden, wenn die Bestellungen so getätigt würden. Also bedarf es einer Möglichkeit, um diese Termin zu berechnen.

 

image

 

Die Tests visualisiert für die Übergabe an die Fachabteilung:

Die Tests visualisiert für die Übergabe an die Fachabteilung

Es handelt sich hier um 7 Tests. Der obere Abschnitt besagt:

Wenn von einer Bestellmappe die die Lieferzeit 5 Tage, die Bearbeitungszeit 1 Tag und das Bestellintervall 14 Tage sind und hierfür die nächsten 3 fiktiven Wareneingänge aus heutiger Sicht berechnet werden sollen:

  • Dann sollte der erste fiktive Wareneingang in 6 Tagen sein
  • Dann sollte der zweite fiktive Wareneingang in 21 Tagen sein
  • Dann sollte der dritte fiktive Wareneingang in 36 Tagen sein
  • Dann sollt es insgesamt 3 Datumsangaben errechnen

Der zweite Abschnitt ist analog zu lesen, wenn die Abkürzungen ausgeschrieben werden. Das Wesentliche daran ist, dass diese Spezifikationen von der Fachabteilungen kommen und diese so sich im Code 1 zu 1 widerspiegeln. Damit können die Entwickler die Brücke schlagen und sich mit den Anwendern in deren Domänensprache unterhalten. Die Spezifikationen garantieren, dass der Code sich wie gewünscht verhält. Die Tests lassen sich automatisiert wiederholen und werden vor jeder neuen Versionsauslieferung durchlaufen.

 

 

Zur Gänze folgen noch der Code für die Tests und die eigentliche Implementierung:

Der Code für die Tests:

   1: [Subject("Fiktive Wareneingänge")]

   2: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und

   3: _das_BI_14T_sind_und_3_fWE_ab_heute_berechnet_werden_sollen

   4: {

   5:     Establish context = () =>

   6:     {

   7:         Clock = new DummyClock();

   8:         Folder = new OrderProposalFolder { DeliveryPeriodInDays = 5, 

   9:                 HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  10:         Sut = new FictitiousIntakes(Clock);

  11:     };

  12:  

  13:     Because of = () =>

  14:     {

  15:         Actual = Sut.FromNow(Folder, 3);

  16:     };

  17:  

  18:     It dann_sollten_exakt_3_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(3);

  19:     It dann_sollte_der_erste_fWE_in_6T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(6));

  20:     It dann_sollte_der_zweite_fWE_in_21T_sein = () => Actual.ElementAt(1).ShouldEqual(Clock.Today.AddDays(21));

  21:     It dann_sollte_der_dritte_fWE_in_36T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(36));

  22:  

  23:     static FictitiousIntakes Sut;

  24:     static OrderProposalFolder Folder;

  25:     static IList<DateTime> Actual;

  26:     static IClock Clock;

  27: }

  28:  

  29: [Subject("Fiktive Wareneingänge")]

  30: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und_das_BI_14T_sind_

  31: und_der_nächste_MT_übermorgen_ist_und_2_fWE_ab_dem_nächsten_MT_berechnet_werden_sollen

  32: {

  33:     Establish context = () =>

  34:     {

  35:         Clock = new DummyClock();

  36:         Folder = new OrderProposalFolder {NextOrderDate = Clock.Now.AddDays(2) ,DeliveryPeriodInDays = 5,

  37:                                          HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  38:         Sut = new FictitiousIntakes(Clock);

  39:     };

  40:  

  41:     Because of = () =>

  42:     {

  43:         Actual = Sut.FromNextFolderDate(Folder, 2);

  44:     };

  45:  

  46:     It dann_sollten_exakt_2_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(2);

  47:     It dann_sollte_der_erste_fWE_in_8T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(8));

  48:     It dann_sollte_der_dritte_fWE_in_23T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(23));

  49:  

  50:     static FictitiousIntakes Sut;

  51:     static OrderProposalFolder Folder;

  52:     static IList<DateTime> Actual;

  53:     static IClock Clock;

  54: }

 

Der produktive Code, der im Programm zur Berechnung verwendet wird:

   1: public class FictitiousIntakes : ICalculateFictitiousIntakes

   2: {

   3:     readonly IClock _clock;

   4:  

   5:     public FictitiousIntakes(IClock clock)

   6:     {

   7:         _clock = clock;

   8:     }

   9:  

  10:     public IList<DateTime> FromNow(OrderProposalFolder folder, int toOrdinal)

  11:     {

  12:         return FromThis(_clock.Today, folder, toOrdinal);

  13:     }

  14:  

  15:     public IList<DateTime> FromNextFolderDate(OrderProposalFolder folder, int toOrdinal)

  16:     {

  17:         return FromThis(folder.NextOrderDate, folder, toOrdinal);

  18:  

  19:     }

  20:  

  21:     private IList<DateTime> FromThis(DateTime startDate, OrderProposalFolder folder, int toOrdinal)

  22:     {

  23:         if (toOrdinal < 1)

  24:             throw new ArgumentOutOfRangeException("toOrdinal");

  25:  

  26:         var firstIntake = startDate.AddDays(folder.DeliveryPeriodInDays + folder.HandlingTimeInDays);

  27:  

  28:         var result = new List<DateTime>();

  29:         for (int currentOrdinal = 0; currentOrdinal <= toOrdinal - 1; currentOrdinal++)

  30:             result.Add(firstIntake.AddDays(currentOrdinal * 

  31:                     (folder.OrderIntervalInDays + folder.HandlingTimeInDays)));

  32:  

  33:         return result;

  34:     }

  35: }

IT Asset Management mit SharePoint 2010 – Teil 1

Den Webcast dazu gibt es hier.

Der Aufbau der erwähnten Inhaltstypen habe ich als Screenshots eingefügt. Seht es eher als Beispielimplementierung, an der ihr euch orientieren könnt. Denkbar wäre z.B. eine Erweiterung dahingehend, dass ihr eine weitere Liste erstellt, in welcher ihr Systemausfälle samt Ursache, Lösung und Ausfalldauer notiert.

 

Überblick Inhaltstypen:

image

 

Inhaltstyp Server, welcher von Element ableitet:

image

Inhaltstyp Physical, welcher von Server ableitet:

image

Inhaltstyp Virtual, welcher von Server ableitet:

image

Inhaltstyp Serverlizenzen, welcher von Element ableitet:

image

Inhaltstyp Systeme/Dienste, welcher von Element ableitet:

image

 

Zusätzlich haben wir noch eine Liste mit den IT-lern und deren Verantwortungsbereich. Hier könnte aber durchaus auch auf eine bestehende Mitarbeiterliste, in welcher die mobile Geschäftsnummer hinterlegt ist, verwendet werden:

image

Integration eines SharePoint Wikis ins ERP-System

Seit ca. 3 Monaten setzen wir erfolgreich auf die Kollaborationsmöglichkeiten, die ein Wiki-System bietet. Als Basis verwenden wir dazu die SharePoint Foundation, welche kostenlos für Windows Server zur Verfügung steht. In dem folgenden Video zeige ich, worauf es zu achten gilt und wie die Firma heco den Wiki Inhalt erfolgreich in das hauseigene ERP-System integriert hat.

In dem Video war leider keine Zeit mehr, um auf weitere Ideen einzugehen und weiterführende Quellen zu nennen, deshalb hole ich dies hiermit nach:

  • Geplant ist eine Ausweitung um Multimediainhalte, allerdings nicht nur aus SharePoint selbst, sondern auch von YouTube
  • Das Aufrufen von ERP-Funktionalität aus dem Wiki
  • Ein Webpart, welches automatisch die Daten des für den Inhalt zuständigen Sachbearbeiters lädt und anzeigt, z.B. die Telefonnummer

Für Rückfragen stehe ich gerne über die üblichen Kommunikationskanäle zur Verfügung. Aber auch wir haben noch Fragen: Kann mir jemand sagen, wo ich das Template für eine Wiki Seite anpassen kann, um z.B. auf allen Seiten immer den letzten Bearbeiter anzuzeigen oder um diverse Links immer am unteren Rand einzublenden?

 

Weiterführende Links:

Universal Service Host

Ich habe soeben in meinem Github Repository ein Projekt zur Verfügung gestellt, mit welchem ihr euch sehr einfach NT Dienste basteln könnt. Einfach das Interface “IAmService” aus der Contracts Assembly in eurem Projekt implementieren und mit Castle.Windsor das Ganze im IoC-Container registrieren und schon könnt ihr euer Programm als Dienst bereitstellen. Das geht im Übrigen auch für eine beliebige Anzahl an Diensten, solange die DLLs dazu im gleichen Verzeichnis wie die EXE-Datei liegen.

Hier ein Video, in dem ich alles erkläre:

Entity Framework Webcasts – UnitOfWork und IoC

 

Ich habe auf YouTube mit einer Webcast Reihe zum Entity Framework begonnen. Ich stelle dabei einen Lösungsansatz vor, der zum Ziel hat das EF in der Anwendungsarchitektur als Persistenzschicht derart zu integrieren, dass Unit und Integration Tests einfach zu implementieren sind. Darüber hinaus soll der Ansatz gängige Prinzipien wie Inversion of Control (IoC), Single Responsibility Principle (SRP), Open-Closed-Principle (OCP) umsetzen, sowie die Abhängigkeiten zum Framework minimieren.

In diesem zweiten Teil zeige ich wie man durch den Einsatz eines IoC-Containers eine direkte Abhängigkeit zur Persistenzschicht vermeiden kann. Darüber hinaus setze ich hier bereits das Konzept einer UnitOfWork ein, welche ich in einem folgenden Teil näher erläutern werde.

Quellen:

Entity Framework Webcasts – DbContext und POCOs

Ich habe auf YouTube mit einer Webcast Reihe zum Entity Framework begonnen. Ich stelle dabei einen Lösungsansatz vor, der zum Ziel hat das EF in der Anwendungsarchitektur als Persistenzschicht derart zu integrieren, dass Unit und Integration Tests einfach zu implementieren sind. Darüber hinaus soll der Ansatz gängige Prinzipien wie Inversion of Control (IoC), Single Responsibility Principle (SRP),  Open-Closed-Principle (OCP) umsetzen, sowie die Abhängigkeiten zum Framework minimieren.

In diesem ersten Webcasts zeige ich wie man die Entitäten als POCOs in einer separaten Assembly ablegt. Obwohl für diesen Zweck nicht notwendig, verwende ich den neuen DbContext, welcher in Version 5 des Entity Frameworks Einzug halten wird.

 

Quellen:

Zwischenfazit Social Media – Teil 5

Strategie braucht Richtung

image

Als erstes Projekt befassten wir uns mit den Social Media Guidelines. Ich kann nur empfehlen, diese als ersten Punkt auf die Agenda zu schreiben, bevor man im Social Web aktiv wird. Die Task Force wurde in mehrere kleine Gruppen aufgeteilt, die jeweils von einem IT-ler betreut wurden. Primär schauten wir uns Guidelines von anderen Firmen an, lasen Empfehlungen dazu (z.B. auf www.chip.de und www.cio.de) und kommunizierten mit der Geschäftsleitung bzw. dem Firmenanwalt. Manche Firmen haben wir direkt angeschrieben, um zu erfragen, wie sie mit dem Thema umgehen. Besonders gewinnbringend waren zwei Vorträge auf der Social Media Night Stuttgart.

Einen wesentlichen Punkt haben wir intensiv diskutiert: Wie werden die Guidelines vermittelt und in welcher Form lässt man sie dem einzelnen Mitarbeiter zukommen. Klar ist, dass es keinen Sinn macht die Richtlinien lediglich in Papierform rauszugeben. In dem Fall muss man sich nicht wundern, wenn sich dann ein Mitarbeiter nicht korrekt verhält. Deshalb setzten wir auf zwei Wege:

  • Informieren bzw. schulen und
  • Unterschrift durch den Einzelnen.

Das Thema Schulung wird in einem späteren Eintrag aufgegriffen, deshalb werde ich an dieser Stelle auf eine Ausführung verzichten. Trotzdem möchte ich darauf hinweisen, welchen Vorteil die in den ersten Teilen erwähnte abteilungsübergreifende Integration bietet: In jeder Abteilung war mindestens eine Person der Gruppe, die bei Fragen kompetent Auskunft geben konnte!

Mit der Unterschrift bestätigte der Mitarbeiter, dass er das Dokument zur Kenntnis genommen hat. Natürlich wollten und wollen wir niemanden einen Strick daraus drehen, sodass es sich nicht um einen bürokratischen Vertrag handelt. Das erkennt man bereits an dem Wort Guidelines, also Richtlinien. Vielmehr gewährleisteten wir damit, dass der Inhalt auch wirklich durchgelesen wurde.

Unsere Social Media Guidelines findet ihr hier.

Zwischenfazit Social Media – Teil 4

Gemeinsame Wissensbasis

image

Wenn man in einem großen, heterogenen Team arbeiten will, so müssen erst alle Beteiligten auf einen gemeinsamen Wissensstand gebracht werden. Auf dieser Basis kann im Anschluss die Kompetenz erweitert werden. Letzteres wird umso wichtiger, je stärker das Thema im Unternehmen integriert wird. Denn – und dessen sollte man sich immer bewusst sein – werden Diskussionen untereinander und Rückfragen rapide zunehmen. Typischerweise entstehen solche Gespräche z.B. beim Kaffeetrinken oder bei Zigarettenpausen. Initiiert werden diese vor allem durch sogenannten Meinungsmacher, zu welchen primär die Abteilungsleiter zählen. In diesen Gesprächen gilt es zu punkten, um Social Media im Unternehmen erfolgreich etablieren zu können. Natürlich ist es gerade für Jugendliche und Auszubildende schwer, eine Thematik – besonders eine derart ambivalente – mit kompetenten Höhergestellten, die langjährige Erfahrung besitzen, zu erörtern! Dies wird umso leichter, je besser man mit der Materie vertraut ist. Allerdings ist es auch umso schwieriger, je stärker das Neue im Gegensatz zu besagter Erfahrung steht.

Dementsprechend waren die ersten Meetings de facto reine Schulungen. Das umfasste auch typische Gegenargumente und deren Entkräftung. Darüber hinaus wurden Domänenbegriffe erläutert, Diskussionen geprobt (auch wenn den Beteiligten das zu dem Zeitpunkt nicht klar war), Vorträge besucht (z.B. die Social Media Night in Stuttgart) und Fachliteratur erarbeitet. Einen wesentlichen Punkt haben wir aber immer wieder genannt: Im Zweifelsfall die Diskussion mit dem Verweis auf später verschieben, dass zuerst mit der Gruppe gesprochen werden muss, bevor dazu eine verbindliche Aussage gegeben werden kann.

Meine Definition eines Social Media Managers

Kürzlich suchte ich auf Wikipedia eine Definition für das Berufsbild „Social Media Manager“ (SMM). Wie ich schon erwartet hatte, gab es dazu keinen direkten Treffer.
Vor nicht allzu langer Zeit las ich in diesem Kontext einen Artikel, der sinngemäß folgendes postuliert: Viele Firmen schreiben die Stelle des Social Media Managers aus ohne genau sagen zu können, welche Aufgaben dieser eigentlich erfüllen können soll und welche Voraussetzungen er mitbringen muss.
Doch laut aktuellen Umfragen (Quelle) sind SMMs stark gefragt.

Für mich also Grund genug meine persönliche Sicht zu schildern. Wohlgemerkt gebe ich hier nur meine Erfahrung darüber, wie ich die Funktion innerhalb meines Unternehmens wahrnehme, wieder:

Da Unternehmensziele wie schnellere Kommunikation, bessere Kollaboration, einfacheres Wissensmanagement und marktdurchdringende PR  sich nicht sich nicht auf eine einzige Abteilung beziehen, macht eine erfolgreich Social Media Strategie die Beteiligung vieler Unternehmensstellen notwendig. Kurz: Social Media ist eine Querschnittsfunktion. Deshalb bedarf es nicht einer Einzelperson zur Implementierung, sondern einer Gruppe.

Der Social Media Manager nimmt in dieser – wie ich sie gerne nenne – „Social Media Task Force“ nun nicht die typische Projektmanager Rolle in Form eines „Leaders“ ein. Diesen prägnanten Unterschied zu verstehen und zu verinnerlichen, fällt vielen Menschen schwer.
Der Grund liegt aber, wenn man den Gedanken von Web 2.0 lebt, auf der Hand: Es gibt hier keine hierarchischen Strukturen. Man denke nur an Wikipedia oder Facebook. Erst dadurch kann die sogenannte „kollektive Intelligenz“ entstehen. Wer nun aber versucht mit einer „altbackenen“ Struktur „Digital Native“-verdauliche Inhalte und Angebote zu generieren, der muss früher oder später scheitern!

Deshalb sehe ich den Social Media Manager als technisch versierten Innovationsgeber, Motivator und Mentor. Er findet immer wieder innovative Ansätze zur Zielerreichung, er hilft den Mitgliedern eigene Ideen zu entwickeln und er motiviert sowohl die idealerweise buntgemischte Gruppe, als auch den Rest des Unternehmens, allen voran die Meinungsmacher. Um diese Funktionsausprägung einnehmen zu können, müssen ihm die Social Media Prozesse und Denke inhärent einverleibt sein. Das bedeutet, dass jeder, der sich nicht intensiv mit den Werkzeugen und Möglichkeiten beschäftigt, nicht für das Berufsbild geeignet ist. Die Person muss aktiv und mit Freude bloggen, twittern und facebooken. Ein pro forma Facebook Account ist nur durch die Web 2.0 Todsünde schlechthin zu toppen: Kein Facebook Konto! Jemand, der selbst noch nie Fussball gespielt hat, sollte auch kein Trainer werden.
Einen Punkt – quasi eine Warnung – gilt es noch zu nennen: SMMs müssen sogenannte „schöpferische Zerstörer“ mit Nerven aus Stahl sein, da sie einen Palast aus revolutionären Gedanken in einer Welt der stillstandliebenden Nein-Sager bauen müssen! Nichts ist ein Tabuthema, was ich ich mit folgendem Zitat polemisch unterstreichen will:

„Der Verweis auf das CI Design ist heutzutage der Joker des Marketings um Innovationen im Keim zu ersticken.“ – Uli Armbruster

Hier meine (Teil-)Auflistung an Skills, die ein SMM mitbringen sollte:
•    In höchstem Maße innovativ und kreativ
•    Guter Teamplayer
•    Interdisziplinär aufgestellt
•    Verständnis für die Geschäftsprozesse
•    Gutes Projektmanagement
•    IT Affinität

Zwischenfazit Social Media – Teil 3

Bunt gemischt

Wie ich bereits in Teil 1 “Das Gelände abstecken” erwähnt habe, hatten wir eine buntgemischte Truppe. Ein weiterer wesentlicher Punkt war, dass die Kollegen auf Grund ihre tagesgeschäft-bezogenen Arbeit nur wenig bis gar keine Erfahrung mit Projektmanagement hatten.

 

Das erste Treffen

Dementsprechend war das Ziel des ersten Treffens das Definieren des kleinsten gemeinsamen Nenners an Wissen. Das fing beispielsweise so an, dass ich die Gruppenmitglieder gefragt habe, ob mir jemand den Begriff Social Media erklären kann. Danach klapperten wir typische Schlagworte wie Microblogging, Blogs, Social Networks, usw. ab. Nach einer guten Stunde hatten wir damit den Ist-Zustand erfasst.

Als nächstes nannten wir Rahmenbedingungen und Ziele. Allerdings möchte ich lieber von Visionen sprechen, da das Wort ‘Ziele’ für mich wenig Raum für Innovation und Agilität lässt. Eine der Visionen war, dass wir in Zukunft Azubi Interviews in Form von Videos anbieten würden. Ein Social Media Profi erkennt hierin schon die Intension zur Verbesserung der Personalakquise (zu den Unternehmensstrategien komme ich in einem späteren Beitrag). Nachdem mit Live Chat, Azubi Interviews, Facebook Fanpage bereits das Interesse der Digital Natives geweckt war, nutzten wir die Chance um “richtig zu motivieren”.

„Ich arbeite sehr viel und sehr gerne, und ich glaube, daß es das Wichtigste für einen Unternehmer ist, daß die Mannschaft stets motiviert wird.“ Willy Bogner

„Einer der besten Wege, die Moral aufzubauen und das Interesse an der Arbeit zu vergrößern, ist das konsequente Delegieren von Verantwortung.“ Cyril Northcote Parkinson

Die zwei Zitate zu Herzen nehmend, erklärten wir der Gruppe welche Möglichkeit sich ihnen hier bietet: Sie konnten einen ganz neuen Weg gehen, der in Zukunft zu einem wesentlichen Eckpfeiler des Unternehmens werden sollte. Wir hatten die volle Unterstützung von der Geschäftsleitung und wir konnten annähernd autark Entscheidungen treffen. Ich erinnere mich an einen Satz im besonderen: “Wie cool ist es denn, wenn euer Ausbilder euch später mal um euren Rat oder um eure Hilfe bittet”.

Nachdem die Ist-Situation ermittelt und das Team “abgeholt” wurde, begannen wir das Projekt zu planen. Mehr dazu in meinem nächsten Beitrag.

Zwischenfazit Social Media – Teil 2

Den Kapitän ins Boot holen

Im ersten Teil der Serie bin ich nur kurz auf darauf eingegangen, dass ich eine Freigabe von der Geschäftsleitung eingeholt habe. Dazu muss man wissen, dass die Firma heco in der Regel auf die Empfehlungen ihrer IT hört, sodass wir nicht nur als Business Enabler agieren, sondern auch als innovativer Ideengeber tätig werden können. Das wird sicherlich bei allen Firmen der Fall sein.

Typische Gegenargument, auf die der ein oder andere stoßen wird, lauten:

  • Das macht für unsere Branche / für unseren speziellen Fall keinen Sinn
  • Unsere Mitbewerber machen das auch nicht
  • Das ist noch zu früh
  • Unsere Kunden nutzen das nicht
  • Da gibt es so viele Negativbeispiele
  • Das ist lediglich ein Hype, der vorbei geht
  • Das ist nur etwas für den privaten Gebrauch

Ich kann euch hier natürlich kein Patentrezept nennen und eine vollständige Erörterung würde den Rahmen sprengen. Allerdings gibt es hierzu schon viel Material im Netz. Trotzdem möchte ich euch ein paar Anregungen mitgeben, auf denen eure Recherchen aufbauen können:

  • Argumentiert immer aus der Sicht von strategischen Unternehmenszielen. Facebook und Co sind lediglich ein Werkzeug, um diese zu unterstützen
  • Wenn man bedenkt, dass das Social Web inzwischen in Autos, Kameras, Fernsehern, Smartphones und vielen weiteren essentiellen Alltagsgegenständen Einzug gehalten hat, dann kann man sicherlich nicht mehr von einem Hype reden, der bald vorüber ist.
  • Innovation bedeutet, dass man dem Kunden etwas bietet, von dem er noch gar nicht wusste, dass er es braucht
  • Der Faktor Zeit: Es ist ein langer Weg, um sich im Social Web zu positionieren. Selbiges gilt für die damit einhergehende innerbetriebliche Umstellung. Die Frage muss also lauten: Ist das Thema auch in 2 Jahren noch nicht für uns interessant, d.h. wird es zu diesem Zeitpunkt für unsere strategischen Ziele (z.B. Kundenbindung) relevant sein?
  • Tatsächlich ist es so, dass Social Media seine Stärken noch nicht so deutlich für den B2B Markt ausspielen kann, wie für den B2C Markt. Aber das ist lediglich eine Frage der Zeit.
  • Wer die Geschichte kennt, der weiß eines: Was im privaten Alltag der Menschen Erfolg hat, wird auch auf das Berufsleben überschwappen. Der Zug kommt und das schneller als man denkt!

Eines sei noch erwähnt: Setzt euch gut mit den Pro und Contra Argumenten auseinander, denn am Schluss muss man die gesamte Belegschaft überzeugen, nicht nur die GL! Ein Boot, auf dem nur die IT und GL rudern, wird euch nicht ans Ziel bringen! Aber hierzu komme ich später noch einmal zu sprechen.

In meinem nächsten Blogeintrag erzähle ich über unseren Einstieg in das Thema mit der Gruppe.

Zwischenfazit Social Media – Teil 1

Das Gelände abstecken

Im Dezember 2010 ging ich auf die Geschäftsleitung zu, um mit ihr über das Thema Social Media zu sprechen. Ich erklärte ihnen, dass ich darin interessante und vor allem auch neue Möglichkeiten sehen würde, um strategische Unternehmensziele zu unterstützen. Dazu wollte ich eine spezielle Arbeitsgruppe gründen, bei der wir es allen Mitarbeitern aus allen Abteilungen unter 26 freistellten daran teilzunehmen. Die IT sollte in diesem Rahmen die Planung und technische Unterstützung übernehmen. Die Freigabe dafür erfolgte sofort, sodass wir nach einer kurzen Vorbereitungszeit meinerseits im Februar 2011 unser erstes Treffen abhalten konnten.

Die Gruppe umfasste 17 Personen. Wir hatten Mitarbeiter aus den Abteilungen Lager, Fertigung, Einkauf, Buchhaltung, Verkauf, Marketing und sogar aus Niederlassungen mit an Bord. Die Altersschere ging vom Jüngsten mit 16, einem neuen Azubi, bis zum Ältesten mit 31, einem unserer IT-ler. Nahezu die Hälfte war in einer Ausbildung. Ein gutes Drittel waren Frauen. Die wenigsten hatten nennenswerte Erfahrungen mit Projektmanagement.

Bis zu diesem Zeitpunkt gab es von uns keinerlei Präsenz in den neuen Kanälen. Viele waren natürlich privat bereits auf Facebook unterwegs, wenige auf Xing und gänzlich niemand war auf Twitter vertreten. Einen Blog führte, wenn ich mich recht erinnere, ebenfalls niemand. Selbst von meinen IT Kollegen hatte bis dato keiner einen Twitter Account oder einen Blog.

 

In meinem nächsten Blogeintrag gehe ich auf die Überzeugungsarbeit ein, die man gegenüber der GL leisten muss.

Smart overriding of the SaveChanges-Method of the EntityFramework

 

As part of our refactoring of the persistence layer implemented with the Entity Framework, we developed an approach for some tricky problems. Although i just discuss one specific scenario, the following solution is probably useful for many different issues:

We are using some objects which won’t be deleted in the classic way by removing them from the underlying database table. Instead, we mark them as deleted, set the purge date and the user who initiated the action. The same procedure exists for creating and modifing objects. You surely can imagine why we are doing this. So how can we achieve this behaviour in a transparent, implicit manner.

Step 1: Set up some interfaces like IHaveCreationDate, IHavePurgeDate and IHaveModificationDate.

   1: public interface IHavePurgeDate

   2: {

   3:     void SetPurgeDate();

   4: }

 

Step 2: Now it depends on your engineering strategy: Code First, Model First or Database First. According to your strategy you might using POCOs or you don’t. First of all, you can implement this solution in every single case, but you might change a little of the implementation. Here’s the way like we are using it with our POCOs (take a look at the ADO.NET Team Blog about the usage of POCOs) automatically generated by the EF:

Auto generated code:

   1: public partial class Worker

   2: {

   3:     public int ID_Bearbeiter { get; set; }

   4:     //some properties

   5:     public int deleted_from { get; set; }

   6:     public Nullable<System.DateTime> purge_date { get; set; }

   7:     public bool isDeleted { get; set; }

   8: }

 

Second part of the partial worker implementation in a separate file (don’t adapt the auto generated class!)

   1: public partial class worker : IHavePurgeDate 

   2: {

   3:     public void SetPurgeDate()

   4:     {

   5:         isDeleted = true;

   6:         purge_Date = DateTime.Now;

   7:         deleted_from = currentUser.ID;

   8:     }

   9: }

If you use Code First engineering, you won’t need a seperate file – of course!

 

Step 3: Build your own context or extend the existing one

If you use the auto generated context of the EF, you will have to create one more partial class to extend this context. If you use you’re own implementation, you can add the following code directly to your class:

   1: public override int SaveChanges()

   2: {

   3:     HandleEntries(ChangeTracker.Entries().ToList());

   4:     return base.SaveChanges();

   5: }

   6:  

   7: internal void HandleEntries(IList<DbEntityEntry> entries)

   8: {

   9:     HandleModifiedEntries(entries);

  10:  

  11:     HandleDeletedEntries(entries);

  12:  

  13:     HandleAddedEntries(entries);

  14: }

  15:  

  16: internal void HandleAddedEntries(IEnumerable<DbEntityEntry> entries)

  17: {

  18:     var createdEntities = entries

  19:         .Where(e => e.State == EntityState.Added)

  20:         .Where(e => e.Entity is IHaveCreationDate)

  21:         .ToList();

  22:  

  23:  

  24:     foreach (var dbEntityEntry in createdEntities)

  25:     {

  26:         ((IHaveCreationDate) dbEntityEntry.Entity).SetCreationDate();

  27:     }

  28: }

  29:  

  30: internal void HandleDeletedEntries(IEnumerable<DbEntityEntry> entries)

  31: {

  32:     var deletedEntities = entries

  33:         .Where(e => e.State == EntityState.Deleted)

  34:         .Where(e => e.Entity is IHavePurgeDate)

  35:         .ToList();

  36:  

  37:     foreach (var dbEntityEntry in deletedEntities)

  38:     {

  39:         ((IHavePurgeDate) dbEntityEntry.Entity).SetPurgeDate();

  40:         dbEntityEntry.State = EntityState.Modified;

  41:     }

  42: }

  43:  

  44: internal void HandleModifiedEntries(IEnumerable<DbEntityEntry> entries)

  45: {

  46:     var modifiedEntities = entries

  47:         .Where(e => e.State == EntityState.Modified)

  48:         .Where(e => e.Entity is IHaveModificationDate)

  49:         .ToList();

  50:  

  51:     foreach (var dbEntityEntry in modifiedEntities)

  52:     {

  53:         ((IHaveModificationDate) dbEntityEntry.Entity).SetModificationDate();

  54:     }

  55: }

 

That’s all. Be aware of two more things:

  • This code sample works with a DbContext of EF 4.1, but you can also use the ObjectStateManager of the ObjectContext in EF 1.0/4.0.
  • The order of handling modification and creating/deletion depends on your intended use.

Generische Factory

Heute will ich mal wieder aus einem Beispiel aus meinem Projektalltag ansprechen.

Es geht um Kalendereinträge in SharePoint. Wir haben 4 verschiedene Typen von Einträgen: Urlaub, Krank, Außer Haus, Im Haus. Im vorliegenden Fall müssen die Typen Urlaub und Krank exportiert werden. Generell ist es so, dass alle Typen bis auf 1-2 Felder die gleichen Eigenschaften besitzen. Ergo arbeite ich mit Vererbung. Die Base Class nennt sich CalendarEntry, von der die zwei Kindsklassen HolidayEntry und SicknessEntry erben (reine Data Transfer Objects).

Da ich die Daten untypisiert von SharePoint erhalte, habe ich mir eine generische Factory gebaut, die mir die 2 besagten Klassen instanziiert und füllt. Das Interface dazu sieht wie folgt aus:

   1: public interface ICalendarEntryFactory<T> where T:CalendarEntry, new()

   2: {

   3:     IEnumerable<T> Build(SPListItemCollection calendarEntries);

   4: }

Die implementierende Klasse enthält also eine Methode namens Build, die mir aus den Kalendereinträgen von SharePoint eine Liste von typisierten Objekten (IEnumberable deswegen, weil es allgemeiner ist als List<T>) zurückgibt. Um welche Objekte es sich handelt, die die Factory erzeugt, wird beim Instanziieren der Factory angegeben, allerdings habe ich den Objekttyp auf CalendarEntry oder eine Ableitung davon eingeschränkt (über where T:CalendarEntry in Zeile 1). Außerdem muss das Objekt (also mein DTO), welches die Factory später erzeugen können soll, einen parameterlosen, öffentlichen Konstruktor haben. Dies gewährleiste ich über das new() in Zeile 1.

Die Klasse, die nun dieses Interface implementiert, sieht wie folgt aus:

1: public class CalendarEntryFactory<T> : ICalendarEntryFactory<T>

where T : CalendarEntry, new()

   2: {

   3:     private readonly IRulesEngine<T> _rulesEngine;

   4:  

   5:     public CalendarEntryFactory(IRulesEngine<T> rulesEngine)

   6:     {

   7:         _rulesEngine = rulesEngine;

   8:     }

   9:  

  10:     public IEnumerable<T> Build(SPListItemCollection calendarEntries)

  11:     {

  12:         ...

  13:     }

  14: }

Noch kurz einen Hinweis zu dem Konstruktorparameter rulesEngine in Zeile 5: Da DTOs niemals Logik enthalten sollten und die Factory, wie der Name schon sagt, lediglich die Funktion erfüllt, dass es Objekte erzeugen kann, benötige ich noch ein Regelwerk, welches die erzeugten DTOs prüft, ob diese korrekt sind. Nur, wenn diese korrekt sind, werden sie der Liste, welche in der Build-Methode zurückgegeben wird, hinzugefügt. Meiner Meinung ist das sehr sinnvoll, um eine stärkere Code Kohäsion zu erhalten. Das entspricht auch den Clean Code Prinzipien Single Responsibility Principle und Seperation of Concerns. Die RulesEngine ist wie man sieht ebenfalls generisch gehalten. Logischerweise mit dem gleichen Typparameter T!

Ein Beispiel, was ich unter anderem mit der RulesEngine prüfe: Ist der Urlaubseintrag vom Typ JAZ (= Jahresarbeitszeit, ohne näher zu erklären, was JAZ bedeutet), so handelt es sich um einen Eintrag, der nicht in das Lohnprogramm exportiert werden darf.

Aber zurück zu der Build-Methode, die mir generisch die Objekte erzeugen soll: Da die Objekte teilweise unterschiedliche Eigenschaften haben (nämlich die, die in den ableitenden Klassen selbst definiert sind), muss ich in der Build-Methode eine Fallunterscheidung machen. Der Code sieht wie folgt aus:

   1: var entriesResult = new List<T>();

   2: T entry;

   3:  

   4: foreach (SPListItem calendarEntry in calendarEntries)

   5: {

   6:     entry = new T();

   7:  

   8:    foreach (SPListItem calendarEntry in calendarEntries)

   9:    {

  10:         //erledige Zuweisungen, die bei allen Eintragstypen gleich sind

  11:  

  12:         if (entry.GetType().Equals(new HolidayEntry().GetType()))

  13:         {

  14:             //erledige spezielle Zuweisungen für den Eintragstyp Urlaub

  15:         }

  16:         else if (entry.GetType().Equals(new SicknessEntry().GetType())) {

  17:             //erledige spezielle Zuweisungen für den Eintragstyp Krank

  18:         }

  19:  

  20:         //erledige Anweisungen, die bei allen Eintragstypen gleich sind

  21:         if (_rulesEngine.Validate(entry)) entriesResult.Add(entry);

  22:    }

  23: }

 

In Zeile 6 sieht man, warum ich bei meinem generischen Constraint dieses “New()” angegeben hatte: Ich muss zur Laufzeit von dem Typ, für den die Factory instanziiert wurde, ein Objekt erzeugen können. Die eigentliche Problematik tritt auf, wenn man die Fallunterscheidung durchführen will (Zeile 12 und 16):

Eine Prüfung auf den Namen des Typs wollte ich nicht durchführen, das ich dann den Namen hardcodiert als Zeichenfolge hätte hinterlegen müssen. Deshalb Instanziiere ich mir in den Zeilen 12 und 16 ein leeres Objekt und hol mir dessen Typ. Der Vorteil ist, dass Refactoring Tools und natürlich auch der Compiler das erkennen. Statt der If-Else-Verschachtelung wollte ich eigentlich eine Switch-Anweisung nehmen (macht natürlich v.a. dann Sinn, wenn man viele solcher Typen hat), allerdings ist hier das Problem, dass der Compiler eine Konstante in den Case-Anweisungen erwartet. Das ist leider nicht gegeben, da ja erst zur Laufzeit bekannt ist, was “entry.GetType()” eigentlich ist.

Peter Hallam, Entwickler bei MS, hatte den Hintergrund dazu bereits 2005 gebloggt. Das macht natürlich Sinn, dass man kein switch auf einen Typen anwenden kann.

Wer allerdings keines der genannten Szenarien aus zuvor erwähntem Blogeintrag bei sich in der Anwendung vorliegen hat, kann sich die Klasse aus dem Blogeintrag switching on types hernehmen, sodass er nicht mit if-else-Verzweigungen arbeiten muss.

How to test static code: Sample with the DateTime-Object

Static Methods are everywhere, especially if the code is from Microsoft. How you work this?

Make an interface with an instance method/member, make a class that implementes the interface an within this instance you can call the static stuff.

Example: The DateTime-object has the member Now, so if you call DateTime.Now, you will get the current time. Unfortunately, can’t test time relevant actions like how long took a print job this way.

First: Declare the interface (VB.NET Code):

   1: Public Interface IClock

   2:     ReadOnly Property Now As DateTime

   3: End Interface

 

Second: Implement it (C# Code):

   1: public class SystemClock : IClock

   2: {

   3:     public DateTime Now

   4:     {

   5:         get { return DateTime.Now; }

   6:     }

   7: }

 

With Fake It Easy you can create a mock like this:

   1: A.Fake<IClock>();

 

Important: Don’t forget to register this in your IoC-Container respectively inject the SystemClock-Object into your object/method that used the DateTime.Now earlier.

%d Bloggern gefällt das: