Jeder kennt Software, die man nicht mehr anfassen möchte. Man sagt, die Software wäre nicht mehr wartbar. Aber was bedeutet das überhaupt?
Die Definition von Wahnsinn ist, immer wieder das Gleiche zu tun und andere Ergebnisse zu erwarten.
von irgendwem.. vielleicht Einstein
Wenn eine Software unwartbar wird, gilt etwas sehr ähnliches.
Die Definition von unwartbar ist, an zwei augenscheinlich gleich funktionierenden Stellen etwas zu ändern und bei beiden das selbe Ergebnis zu erwarten... das es dann aber nicht ist!
von Hannes Pries... nach einen entsprechenden Tag
Oft ist solche Software organisch gewachsen. Organisch gewachsen bedeutet in diesem Fall, dass man nie das grundlegende System erweitert und verbessert hat, sondern einfach neue Funktionen drum herum gebaut hat. Auch wird oft Code kopiert und im Laufe der Zeit immer nur die einzelnen Code-Blöcke angepasst, wo es nötig ist und die anderen so belassen. Besser wäre es gewesen, den Code auszulagern, so dass an allen Stellen die Änderungen greifen.
Oft sind es gar nicht die ganz alten Code-Teile der aller ersten Version, die die Probleme machen, weil diese noch einem Konzept folgten. Mit der Zeit ist das Konzept verloren gegangen und die damaligen Entwickler sind oft schon lange nicht mehr dabei. Es gibt natürlich keine Doku, Namespaces und eine besonders übersichtliche Sortierung von Klassen sind meistens nicht vorhanden, gab es zu der Zeit einfach nicht oder wurden durch die ganzen Erweiterungen selbst zerstört.
Mit der Zeit setzt sich die Annahme durch, dass der damalige Code einfach schlecht war und man es erneuern sollte, wenn man etwas neues programmiert. Das führt dazu, dass jeder das auch macht und plötzlich 3-4 Implementierungen für ein und das selbe gibt.
Dumm nur, dass auch alle diese Implementierungen auch immer verwendet werden und so sich niemand traut eine der Implementierungen abzuschalten, weil es nicht der eigene Code ist und man sich nicht sicher ist, ob es wirklich 100%ig den selben Funktionsumfang hat.
Aber irgendwann muss man daran und dann baut man einfach etwas wieder ran und entfernt nicht einfach die Implementierung und nutzt die aktuellste.
Lustig ist auch, dass man oft gegen Ende der Lebenszeit einer Software, wenn man die Strukturen für einen Rewrite analysiert, feststellt, was das grundlegende alte Framework schon alles konnte und vieles was man später mit viel Aufwand eingebaut hat, schon lange da war. Oft auch sogar in einer gar nicht so schlechten Implementierung.
Jeder hat bei so etwas genau so mitgemacht, oder? Zeitdruck... neu bauen und nicht alten Code lesen. Wirklich mal mysqli-Funktionen gegen PDO ersetzen? Wäre toll, aber wer will sich wirklich die Arbeit machen, weil es ja doch alles irgendwie funktioniert. Das System verwendet viele PHP-Dateien und keine zentrale index.php? Kann man ja schnell bauen.. macht man aber nie und muss in vielen Dateien oft die selben Dinge ändern.
Was ist die Lösung? Sich klar werden, dass es nichts mehr bringt und das System sauber neu schreiben. Nicht für sich (naja.. doch irgendwie schon), sondern für andere Entwickler. Es muss ein System sein, wo man von ausgeht, dass es für jeden Fall eine Lösung mitbringt. Das System wird den selben Weg gehen und man muss versuchen schon ganz am Anfang anfangen gegen zu steuern auch noch kein anderer daran mit arbeitet.
Keep it simple! Keep it clean!
hat das schon mal wer anderes gesagt? Bestimmt!
Wenig Code, klare Interfaces mit 100%ig aussagekräftige Namen. Die Codevervollständigung ist die Suche nach der richtigen Klasse, also müssen alle Klassen richtig benannt sein.
Es ist alle schwerer als man denkt, aber Frameworks und Standards wie PSR bei PHP helfen da sehr. Sich einfach auf den Stil und die Konventionen des Frameworks einlassen und sich aneignen. Dann klappt es schon. Auch wenn es schwer ist.
Eine wichtige Erkenntnis das Code-Element in Shopware Einkaufswelten betreffend ist, dass es Smarty unterstützt. Wer also im HTML-Teil direkt JavaScript verwenden will muss etwas mit den geschweiften Klammern aufpassen. Das gilt besonders, wenn man einfach minimized Code-Snippets von einem Anbieter in die eigene Seite integrieren möchte.
function (x) {
alert(x);
}
oder
{literal}
function(x){alert(x);}
{/literal}
funktionieren.
Ich hatte es schon fertig und dachte ich könnte es später auch mal im Community Store anbieten.. aber dann kam mir schon jemand zu vor und dann auch noch kosten los.
Also hier einfach mein Plugin als Code für jeden:
<?php
namespace HPrExportAttrExtend;
use Doctrine\DBAL\Connection;
use Shopware\Components\Plugin;
class HPrExportAttrExtend extends Plugin{
private $ignoreFields = [
'articleID',
'articledetailsID',
'attr1',
'attr2',
'attr3',
'attr4',
'attr5',
'attr6',
'attr7',
'attr8',
'attr9',
'attr10',
'attr11',
'attr12',
'attr13',
'attr14',
'attr15',
'attr16',
'attr17',
'attr18',
'attr19',
'attr20',
];
public static function getSubscribedEvents(){
return [
'sExport::sCreateSql::after' => 'extendExportArticleSql',
];
}
public function extendExportArticleSql(\Enlight_Hook_HookArgs $args){
$sql = $args->getReturn();
$fields = $this->getAllColumnNames();
if(count($fields) > 0){
$fields[] = 'at.attr20';
$sql = preg_replace("/at\.attr20/i", implode(', ', $fields), $sql);
}
$args->setReturn($sql);
}
private function getAllColumnNames(){
/** @var Connection $dbal */
$dbal = $this->container->get('dbal_connection');
$sql = 'SHOW columns FROM :tablename';
$stmt = $dbal->prepare($sql);
$stmt->execute(['tablename' => 's_articles_attributes']);
$rows = $stmt->fetchAll();
$fields = [];
foreach ($rows as $row){
$name = $row['field'];
if(!in_array($name, $this->ignoreFields)){
$fields[] = 'at.' . $name;
}
}
return $fields;
}
}
Für mein Projekt Mein-Online-Adventskalender habe ich die alte Fancybox ersetzt und eine eigene kleine Lösung geschrieben. Kein JQuery, kein AngularJS.. einfaches kleines altes JavaScript.
function MediaViewer(){
this.dialog = null;
this.mediaContainers = [];
this.elements=[];
this.init=function(){
var elements=document.querySelectorAll("[mv-dialog]");
for(var i=0; i<elements.length;i++){
var func=function(controller){
return function(){
controller.close();
};
};
elements.addEventListener("click", func(this), true);
this.dialog = elements;
this.dialog.style.display="none";
}
this.close();
var elements=document.querySelectorAll("[mv-item]");
for(var i=0; i<elements.length;i++){
var el = elements;
var src="";
if(el.getAttribute("mv-src")){
src=el.getAttribute("mv-src");
}
else if(el.getAttribute("src")){
src=el.getAttribute("src");
}
var contEl={
id:this.elements.length,
src:src,
type: el.getAttribute("mv-item").length > 0 ? el.getAttribute("mv-item") : el.nodeName
};
this.elements[contEl.id] = contEl;
var func=function(controller, id){
return function (){
controller.openDialog(id);
};
};
el.addEventListener("click", func(this, contEl.id), true);
}
var elements=document.querySelectorAll("[mv-container]");
for(var i=0; i<elements.length;i++){
var el = elements;
var cont={
element:el,
type:el.getAttribute("mv-container").length >0 ? el.getAttribute("mv-container") : el.nodeName
}
var func=function(controller){
return function(){
controller.close();
};
};
elements.addEventListener("click", func(this), true);
this.mediaContainers[this.mediaContainers.length] = cont;
}
};
this.close=function(){
var classes = this.dialog.getAttribute("class");
if(!classes){
classes = "";
}
if(classes.length == 0 || classes.match(/mvvisible/)){
classes=classes.replace(/mvvisible/, "");
classes += " mvinvisible";
this.dialog.setAttribute("class", classes);
}
};
this.openDialog=function(id){
this.dialog.style.display="";
for(var i=0;i<this.mediaContainers.length;i++){
var cont=this.mediaContainers;
if(cont.type.toUpperCase() == this.elements[id].type.toUpperCase()){
cont.element.style.display="";
cont.element.setAttribute("src",this.elements[id].src);
}
else{
cont.element.style.display="none";
}
}
var classes = this.dialog.getAttribute("class");
if(!classes){
classes = "";
}
if(!classes.match(/mvvisible/)){
classes=classes.replace(/mvinvisible/, "");
classes += " mvvisible";
this.dialog.setAttribute("class", classes);
}
}
this.init();
}
Die Anzeige, die man vor dem schliessenden Body-Tag einbauen muss:
<div mv-dialog style="display:none">
<img mv-container="img"/>
<video mv-container="video" autoplay mute></video>
</div>
<script type="text/javascript">
new MediaViewer();
</script>
Passendes CSS:
div[mv-dialog]{
position:fixed;
display:flex;
flex-flow: column;
top:0vh;
left:0vh;
width:100%;
height:100%;
background-color:rgba(0,0,0,0.8);
}
img[mv-item], video[mv-item]{
cursor:pointer;
}
div[mv-dialog].mvvisible{
animation-name:animvvisible;
animation-duration:1s;
display:default;
}
@keyframes animvvisible{
0%{opacity:0;transform:scale(0.5);}
90%{transform:scale(1);}
100%{opacity:1;}
}
div[mv-dialog].mvinvisible{
animation-name:animvinvisible;
animation-duration:2s;
height:0;
}
@keyframes animvinvisible{
0%{opacity:1;transform:scale(1);height:100%;}
90%{transform:scale(0.5);}
100%{opacity:0;height:0;}
}
img[mv-container]{
display:block;
margin-left:auto;
margin-right:auto;
margin-top:5vh;
margin-bottom:auto;
max-width:98vw;
max-height:90vh;
vertical-align:middle;
cursor:pointer;
border-radius:5px;
}
video[mv-container]{
display:block;
margin-left:auto;
margin-right:auto;
margin-top:5vh;
margin-bottom:auto;
max-width:98vw;
max-height:90vh;
vertical-align:middle;
cursor:pointer;
}
Damit kann man ganz einfach Pop-Ups zur Mediendarstellung erstellen.
<img mv-item src="test.jpg">
<button mv-item="img" mv-src="test.jpg">click to open</button>
<img mv-item mv-src="test.jpg" src="test_thumb.jpg">
<button mv-item="video" mv-src="test.mp4">open video</button>
Man ganz einfache eigene Container in den Dialog einbauen. Der Name wird bei mv-item angegeben. Sollten nur die Tags wie mv-item angegeben sein versucht die Logik im Dialog ein Element des selben Typs (img, video, etc) zu finden.
Wenn man Code in seinen Blog-Posts verwendet gilt dafür das Selbe wie für den Text. Der Code muss gut lesbar sein. Strukturiert und nicht in seiner Masse den Leser erschlagen. Wenn man einen großen Block Code vor sich hat, fühlt man sich sehr schnell davon erschlagen. Hier hilft selbst minimales Syntax-Highlighting sehr.
Deswegen wollte ich gerne bei dem Code in meinem Blog richtiges Syntax-Highlighting haben. Den PHP-Code wollte ich nicht anfassen und dachte mir: "Da gibt es doch bestimmt was in JavaScript". 1 Minute später hatte ich eine Lösung gefunden.
highlight.js lässt sich ganz schnell integrieren und man muss nicht mal was downloaden.
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.8.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.8.0/highlight.min.js"></script>
Wenn man nun keine code-Tags verwendet, sondern wie ich es gemacht hatte, ein DIV mit spezieller CSS-Klasse verwendet hatte, muss man highlight.js noch sagen, dass dieser verwendet werden soll. Dies macht man ganz einfach über JQuery.
<script type="text/javascript">
hljs.configure({useBR: true});
$('div.bcode').each(function(i, block) {
hljs.highlightBlock(block);
});
</script>
Und schon hat man alle Quellcodes mit Syntax-Highlighting. Man kann es mit Vorgaben, welches Highlighting man gerne möchte (XML, PHP, Java, etc), aber selbst ohne diese Angaben sind die Resultate schon wirklich super.