Posts mit dem Label Software-Entwicklung werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Software-Entwicklung werden angezeigt. Alle Posts anzeigen

2012-04-13

Kanban und Kaizen

Folgendes Video zeigt mein Team und beschreibt was Kanban & Kaizen (Signalkarten und Veränderung zum Besseren) für uns gebracht haben:
Kurz (aus meiner Sicht als Entwickler):
Vorteile:
  • Focus. 
  • Messbarkeit. 
  • Kleine Schritte, also kontinuierliche Veränderungen, statt mühsame - oft als künstlich wahrgenommene - Zeremonien.
  • Man muss nichts wirklich neu lernen. Man macht sich nur bewußt was wer wie warum tut.
Nachteile:
  • Keine. Wenn man plötzlich beschließt aufzuhören, hat man trotzdem gewonnen, da man sein Verhalten bereits geändert hat, ohne die Anstrengung zu spüren.

2011-04-29

Bis zum Hals in Arbeit

Als ich neulich den Ausspruch eines Teamleiters hörte, daß das Team bis zum Hals in Arbeit stecke, mußte ich sofort an das untige Bild denken:

Tja diese kreativen Workaholics ...
Nachtrag: Nach meinem letzten Eintrag war auch ich länger als gewollt in der Firma (Ausnahmen bestätigen die Regel)

2010-05-20

Stilblüten und Codeleichen

  • Aus der Rubrik "Neulich im Quelltext":
  • PROCEDURE displayData(parameter1, parameter2)
    BEGIN
    IF parameter1 is not null OR 1=1 THEN --just for testing
    ...a huge block of code to display the data...
    END IF;
    END;
    Kein ELSE-Teil und seit langer Zeit unverändert im Repository

  • Querdenker der Woche:
  • -- zu ratendes Objekt
    v_objectId := getCustomerObject();
    Es ging übrigens ums Rating (also: Beurteilung der Bonität) nicht ums Raten (im Sinne von Rätselraten)

  • Querschläger der Woche:
  • v_objectId := nimmIrgendeinObjektAusDerListe(liste);
    Wie jetzt irgendein!?
    Das Erste/Letzte würde zumindest verhersagbare Ergebnisse liefern.
    So weiß man zwar, daß man eine Objekt-ID bekommt, aber es könnte auch bei unveränderter Liste jedesmal eine andere sein.

  • Daily Business:
  • BEGIN
    WHILE fehlerVorhanden() LOOP
    behebeFehler();
    EXIT WHEN schnauzeVoll();
    END LOOP;
    END;

    2009-12-22

    Fluchomat

    Neulich hörte ich aus der Ecke unseres Testers so merkwürdige Flüche ...
    Ja, ja, so ein Fluchomat ist schon eine tolle Sache.
    Tip: Koordinate [4,3] ist mein Favorit

    Nachtrag: Zum Schluß hörte ich dann aber doch noch den unten rechts ;)

    2009-06-16

    EJB-Konfiguration auf glassfish manipulieren ohne Reboot mittels MBeans

    Hintergrund: Nachdem ein EJB (Web-)Service auf dem Application Server (glassfish) deployed wurde, soll die Konfiguration geändert werden ohne den Service neu zu deployen oder gar den Server neu starten zu müssen.

    Daher scheidet ein config.xml im jar-File des Service aus. Ebenso wie die irreführend als dynamisch benannten Server-Properties (hier ist vor dem Wirksamwerden immer ein kompletter Serverneustart nötig)

    Lösung: Custom MBeans

    Und so kann es gehen (schöner geht immer):
    * Ein Interface ConfigMBean
    * Eine abstrakte Klasse Config
    * Zwei Implementationen Development und Production
    * Einen ConfigReader (der nur mit Config hantiert)
    * Ein EJB namens DemoBusiness (der via ConfigReader auf ein MBean zugreift)

    Als Schmankerl cached der ConfigReader und alle ConfigMBean Implementierungen [Development und Production] besitzen eine hardverdrahtete DefaultConfig. Somit funktioniert dieser auch, wenn gerade kein MBean registriert/deployed ist.

    Der ConfigReader registriert sich beim Config(MBean), um über Änderungen von Attributen benachrichtigt zu werden. Der Einfachheit halber wird dann immer die gesamte Config (Cache) ungültig und beim nächsten getConfig erneuert.

    Zusätzlich registriert sich der ConfigReader noch beim Application Server direkt als Listener für unregister-Nachrichten, damit bei unregister/undeploy ebenfalls die Config ungültig wird.

    Wichtig: Am Ende (der Benutzung) muß man sich als Listener mittels releaseConfigListeners wieder deregistrieren (z.B. in ejbRemove oder einem finally-Block).

    * EJB normal deployen
    * Alle class-Files aus de.isolvedit.config.category nach domain-dir/applications/mbeans kopieren
    * folgendes Kommando (zu beachten: Nur via asadmin oder glassfish-GUI gesetzte Attribute werden gespeichert - bleiben also auch nach einem Serverneustart erhalten)
    asadmin create-mbean --user adminuser --name de.isolvedit.config.category.Development de.isolvedit.config.category.Development --attributes SalesOrganisation=1100:DistributionChannel=10: Category=10:ShipCondition=ST:ChangeUser=unknown:Unit=ST: OrderTypeReturnFlag=ZRE: PositionTypeBuy=?:PositionTypeRent=!
    Anmerkung: attr=value:attr=value ohne Leerzeichen(!)

    So sieht das dann auf dem glassfish aus:


    Hier schonmal die Ausgabe im Logfile:
    [DemoBusiness.doSomething()] : start
    [ConfigReader.connectConfigListener()] : add to notification listeners...
    [ConfigReader.connectConfigListener()] : ...done:
    [ConfigReader.getConfig()] : MBean from server
    [ConfigReader.getConfig()] : MBean name: user:impl-class-name=de.isolvedit.config.category.Development, name=de.isolvedit.config.category.Development, server=server
    [ConfigReader.getConfig()] : unit=ST
    [ConfigReader.getConfig()] : shipCondition=ST
    [ConfigReader.getConfig()] : positionTypeRent=FASEL
    [ConfigReader.getConfig()] : category=10
    [ConfigReader.getConfig()] : distributionChannel=10
    [ConfigReader.getConfig()] : positionTypeBuy=BLA
    [ConfigReader.getConfig()] : orderTypeReturnFlag=ZRE
    [ConfigReader.getConfig()] : changeUser=unknown
    [ConfigReader.getConfig()] : salesOrganisation=1100
    [ConfigReader.handleNotification()] : notification message: config changed sequence: 10



    Config

    package de.isolvedit.config.category;

    public interface ConfigMBean {
    public String getSalesOrganisation();
    public void setSalesOrganisation(String salesOrganisation);
    public String getDistributionChannel();
    public void setDistributionChannel(String distributionChannel);
    public String getCategory();
    public void setCategory(String category);
    public String getShipCondition();
    public void setShipCondition(String shipCondition);
    public String getChangeUser();
    public void setChangeUser(String changeUser);
    public String getUnit();
    public void setUnit(String unit);
    public String getOrderTypeReturnFlag();
    public void setOrderTypeReturnFlag(String orderTypeReturnFlag);
    public String getPositionTypeBuy();
    public void setPositionTypeBuy(String positionTypeBuy);
    public String getPositionTypeRent();
    public void setPositionTypeRent(String positionTypeRent);
    }



    ConfigBean

    package de.isolvedit.config.category;

    import javax.management.AttributeChangeNotification;
    import javax.management.Notification;
    import javax.management.NotificationBroadcasterSupport;

    public abstract class Config extends NotificationBroadcasterSupport implements ConfigMBean {

    public static final String CONFIG_CHANGED = "config changed";

    // to set attributes use asadmin or glassfish-GUI !!
    // the following properties are set to null, if deployed via glassfish!
    // further details: http://docs.sun.com/app/docs/doc/820-4336/gbdzi?a=view

    protected String category;
    protected String changeUser;
    protected String distributionChannel;
    protected String orderTypeReturnFlag;
    protected String positionTypeBuy;
    protected String positionTypeRent;
    protected String salesOrganisation;
    protected String shipCondition;
    protected String unit;

    private long sequenceNumber = 1L;

    // there is no abstract static -> so this is the method to override in subclasses!
    public static Config DefaultConfig() {
    return null;
    };

    public synchronized void configChanged() {
    Notification n = new AttributeChangeNotification(this, sequenceNumber++, System.currentTimeMillis(),
    CONFIG_CHANGED, "*", "String", null, null);
    sendNotification(n);
    }

    public String getSalesOrganisation() {
    return salesOrganisation;
    }
    public void setSalesOrganisation(String salesOrganisation) {
    this.salesOrganisation = salesOrganisation;
    configChanged();
    }
    public String getDistributionChannel() {
    return distributionChannel;
    }
    public void setDistributionChannel(String distributionChannel) {
    this.distributionChannel = distributionChannel;
    configChanged();
    }
    public String getCategory() {
    return category;
    }
    public void setCategory(String category) {
    this.category = category;
    configChanged();
    }
    public String getShipCondition() {
    return shipCondition;
    }
    public void setShipCondition(String shipCondition) {
    this.shipCondition = shipCondition;
    configChanged();
    }
    public String getChangeUser() {
    return changeUser;
    }
    public void setChangeUser(String changeUser) {
    this.changeUser = changeUser;
    configChanged();
    }
    public String getUnit() {
    return unit;
    }
    public void setUnit(String unit) {
    this.unit = unit;
    configChanged();
    }
    public String getOrderTypeReturnFlag() {
    return orderTypeReturnFlag;
    }
    public void setOrderTypeReturnFlag(String orderTypeReturnFlag) {
    this.orderTypeReturnFlag = orderTypeReturnFlag;
    configChanged();
    }
    public String getPositionTypeBuy() {
    return positionTypeBuy;
    }
    public void setPositionTypeBuy(String positionTypeBuy) {
    this.positionTypeBuy = positionTypeBuy;
    configChanged();
    }
    public String getPositionTypeRent() {
    return positionTypeRent;
    }
    public void setPositionTypeRent(String positionTypeRent) {
    this.positionTypeRent = positionTypeRent;
    configChanged();
    }
    }



    Development

    package de.isolvedit.config.category;

    public class Development extends Config {

    public static Development DefaultConfig() {
    Development config = new Development();
    config.setSalesOrganisation("1100");
    config.setDistributionChannel("10");
    config.setCategory("10");
    config.setShipCondition("ST");
    config.setChangeUser("unknown");
    config.setUnit("ST");
    config.setOrderTypeReturnFlag("ZRE");
    config.setPositionTypeBuy("?!?");
    config.setPositionTypeRent("!??");

    return config;
    }
    }



    Production

    package de.isolvedit.config.category;

    public class Production extends Config {

    public static Production DefaultConfig() {
    Production config = new Production();
    config.setSalesOrganisation("1100");
    config.setDistributionChannel("10");
    config.setCategory("10");
    config.setShipCondition("ST");
    config.setChangeUser("uwEE");
    config.setUnit("ST");
    config.setOrderTypeReturnFlag("ZRE");
    config.setPositionTypeBuy("BUY");
    config.setPositionTypeRent("RNT");

    return config;
    }
    }



    ConfigReader

    package de.isolvedit.config;

    import java.util.Map;

    import javax.management.AttributeChangeNotification;
    import javax.management.MBeanServerConnection;
    import javax.management.Notification;
    import javax.management.NotificationListener;
    import javax.management.ObjectName;

    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.log4j.ConsoleAppender;
    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;
    import org.apache.log4j.PatternLayout;

    import com.sun.appserv.management.base.Util;
    import com.sun.enterprise.admin.server.core.jmx.AppServerMBeanServerFactory;

    import de.isolvedit.config.category.Config;

    public class ConfigReader implements NotificationListener {
    public final static String MBEAN_SERVER_DELEGATE = "JMImplementation:type=MBeanServerDelegate";
    public final static String MBEAN_UNREGISTERED = "JMX.mbean.unregistered";

    /**
    * static log4j-logger
    */
    private static final Logger LOGGER = Logger.getLogger(ConfigReader.class);

    private MBeanServerConnection server = null;
    private Config configCache = null;
    private boolean useCachedConfig = false; // force update
    private boolean connectedToServerAsListener = false;
    private ObjectName configObjectName = null;

    public ConfigReader(Config defaultConfig) {
    this.configCache = defaultConfig;
    this.configObjectName = getObjectName();
    // connect to glassfish
    server = AppServerMBeanServerFactory.getMBeanServerInstance();
    }

    // for testing purposes only!
    public ConfigReader(Config defaultConfig, MBeanServerConnection server) {
    this.configCache = defaultConfig;
    this.configObjectName = getObjectName();
    this.server = server;
    LOGGER.addAppender(new ConsoleAppender(new PatternLayout()));
    LOGGER.setLevel(Level.ALL);
    }

    // react on config changes and mbean unregister events
    // if attributes changed invalidate cached config (force update on next getConfig call)
    // if config mbean is unregistered invalidate listenmode (force reconnect on next getConfig call)
    public synchronized void handleNotification(Notification notification, Object handback) {
    try {
    if (notification instanceof AttributeChangeNotification) {
    LOGGER.info("notification message: " + notification.getMessage() + " sequence: "
    + notification.getSequenceNumber());
    if (Config.CONFIG_CHANGED.equals(notification.getMessage())) {
    useCachedConfig = false; // force update
    }
    } else if (MBEAN_SERVER_DELEGATE.equals(notification.getSource().toString())
    && MBEAN_UNREGISTERED.equals(notification.getType()) && !server.isRegistered(configObjectName)) {
    releaseConfigListener();
    }
    } catch (Exception e) {
    LOGGER.error("caught exception", e);
    }
    }

    // return config
    // try to connect config mbean and register as attribute change and unregister listener
    // try to read from config mbean else use default/cached version
    public Config getConfig(boolean cachedVersion) {
    connectConfigListener();
    if (cachedVersion || useCachedConfig) {
    LOGGER.info("MBean from cache");
    } else {
    LOGGER.info("MBean from server");
    try {
    LOGGER.info("MBean name: " + configObjectName);

    @SuppressWarnings("unchecked")
    Map properties = BeanUtils.describe(configCache);

    for (String property : properties.keySet()) {
    if (!property.equalsIgnoreCase("Class") && !property.equalsIgnoreCase("NotificationInfo")) {
    Object value = server.getAttribute(configObjectName, initCap(property));
    LOGGER.info(property + "=" + value.toString());
    BeanUtils.setProperty(configCache, property, value);
    }
    }

    useCachedConfig = true;

    } catch (Exception e) {
    LOGGER.error("caught exception", e);
    useCachedConfig = false;
    }
    }
    return configCache;
    }

    public boolean connectConfigListener() {
    boolean result = false;
    if (!connectedToServerAsListener) {
    LOGGER.info("add to notification listeners...");
    try {
    server.addNotificationListener(configObjectName, this, null, null);
    server.addNotificationListener(getMBeanServerDelegateObjectName(), this, null, null);
    connectedToServerAsListener = true;
    useCachedConfig = false; // force update
    LOGGER.info("...done: ");
    result = true;
    } catch (Exception e) {
    LOGGER.warn("...failed: " + e.getMessage());
    e.printStackTrace();
    result = false;
    }
    } else {
    result = true;
    }
    return result;
    }

    public boolean releaseConfigListener() {
    boolean result = false;
    if (connectedToServerAsListener) {
    try {
    LOGGER.info("remove from notification listeners...");
    if (server.isRegistered(configObjectName)) {
    server.removeNotificationListener(configObjectName, this);
    }
    server.removeNotificationListener(getMBeanServerDelegateObjectName(), this);
    LOGGER.info("...done");
    result = true;
    } catch (Exception e) {
    LOGGER.error("...fail", e);
    result = false;
    }
    connectedToServerAsListener = false;
    } else {
    result = true;
    }
    return result;
    }

    public static String initCap(String input) {
    if (input == null || input.isEmpty()) {
    return input;
    }
    return input.substring(0, 1).toUpperCase() + input.substring(1);
    }

    public ObjectName getObjectName() {
    try {
    return new ObjectName("user:impl-class-name=" + configCache.getClass().getName() + ",name="
    + configCache.getClass().getName() + ",server=server");
    } catch (Exception e) {
    return null;
    }
    }

    public static ObjectName getMBeanServerDelegateObjectName() {
    return (Util.newObjectName(MBEAN_SERVER_DELEGATE));
    }
    }



    DemoBusiness

    package de.isolvedit.business;

    import de.isolvedit.config.category.Config;
    import de.isolvedit.config.category.ConfigReader;
    import de.isolvedit.config.category.Development;

    @Stateless
    public class DemoBusiness implements DemoBusinessLocal {

    private static final Logger LOGGER = Logger.getLogger(ConfigReader.class);

    private ConfigReader conf = new ConfigReader(Development.DefaultConfig());
    //private ConfigReader conf = new ConfigReader(Production.DefaultConfig());

    public String doSomething(){
    LOGGER.info("start");
    String result = null;
    try {
    try {
    Config config = conf.getConfig(false);
    result = config.getSalesOrganisation();
    } catch (Exception e) {
    LOGGER.error("caught exception", e);
    }
    } finally {
    conf.releaseConfigListener();
    }

    return result;
    }

    }



    Nachtrag: Spannend wäre sicher auch sich direkt als Listener für Attributänderungen beim Application Server zu registrieren ... Kommentare sind willkommen.
    Nachtrag 2: Obiges funktioniert erst ab glassfish 2.1

    2009-04-27

    The Paradox of Choice - Why More Is Less

    Verdammt guter Vortrag (englisch) zum Thema: Warum viele Optionen zu haben oft weniger Wert ist, als weniger Wahlfreiheit.

    Was Freiheit in diesem Zusammenhang bedeutet wird gleich am Anfang aufbereitet.
    Eine der Aussagen: Wenn Menschen zu viele Möglichkeiten haben, wählen sie lieber keine, als ggf. eine schlechte Variante. Und selbst wenn sie eine bessere gewählt haben, als sie dies aus einer kleineren Menge hätten tun können, fühlen sie sich oft schlechter. Und zwar, weil sie dank erhöhter Auswahl Perfektion erwarten, dies aber nicht "beweisbar" ist, solange sie nicht alle Möglichkeiten getestet haben (was unmöglicher wird, je größer die Vielfalt).
    Dauer: ca 1 Stunde



    Link: http://video.google.com/videoplay?docid=6127548813950043200&hl=de

    Kleiner Nachtrag: Software-Entwickler/Architekten sollten sich von diesen allgemeinen IT unbelasteten Erkenntnissen beeinflussen lassen. Weniger ist oft mehr. Wobei die im letzten Teil angesprochene Verwendung von Agenten durchaus hilfreich sein kann (besonders schön das Beispiel mit den griechischen Restaurants).

    Wieso Entscheider eine maximal Fünfpunkt Präsentation (in 48-Punkt Schrift) der abzuwägenden Risiken haben wollen, wird mir jetzt auch klarer ;)