Verbessere deine Developer-Experience mit Task

22.02.2023Raffael Schneider
Tech Developer Experience Productivity Development Integration Task Runner Build Automation workflowcomposition DevOps

taskfile-banner

Seit geraumer Zeit hats bei mir Klick gemacht und habe endlich verstanden, warum vermehrt von dem Aspekt der Developer Experience die Rede ist. Genau wie der Durschnittskonsument eines digitalen Angebots dessen Qualität dadurch misst, wie gut die sogenannte User Experience ihn begleitet, so ist der Entwickler, welcher auf der anderen Seite des Lebenszyklus dieses Angebots steht, also an dessen Implementation und Bereitstellung steht, genauso stark von einer sogenannte Developer Experience geprägt. Oder anders gesagt, der Entwickler als solches ist mittlerweile auch eine rare Ressource geworden, welche von potentiellen Arbeitgebern unter anderem auch durch attraktive Developer Experiences umworben werden möchte. Das betrifft nicht nur den eigentlichen Technologie-Stack, sondern auch den Umgang damit.

Als Entwickler von digitalen Angeboten aller Art, bin ich selber sehr stark an dieser Thematik der Developer Experience interessiert. Aus diesem Grund möchte ich im heutigen TechUp einem Teilbereich genau dieses Themas nachgehen und dir, dem Leser, einen weiteren Aspekt veranschaulichen, mit dem man potentiell für eine bessere Developer Experience sorgen kann.

Development Integration

Die Developer Experience wird in erster Linie durch den eigenen Workflow definiert. Das heisst: Wie viele einzelne Schritte braucht es bis eine neue Iteration der Abarbeitung von Tasks entsteht. Dabei kann eine Iteration beispielsweise das Einfügen des ersten Implementationsversuchs eines gegeben Features sein, welcher im zweiten Schritt kompiliert und im dritten Schritt in einer Runtime zu Testzwecken hochgefahren wird. Die einzelnen Steps sind oft vielzählig und sind stark voneinander abhängig. Bevor ein neuer Code-Block auf der lokalen Maschine getestet werden kann, muss der Code kompiliert werden können. Das Kompilat muss den Weg in eine Runtime finden, und das kann nur passieren, wenn der Code kompiliert. Somit ist die Reihenfolge des Workflows streng vorgegeben. Jede Iteration kostet Zeit und Nerven. Man möchte also für effiziente Prozesse sorgen und die einzelnen Steps so einfach wie möglich halten. Und das führt mich zum Thema der Entwicklerintegration.

Was ich gerade erwähnt habe, war eine Komposition von Steps, welche den gesamten Entwickler-Workflow ausmachen. Die einzelen Bausteine sind dabei oft Kommandozeilenbefehle. Solche Befehle lassen sicht gut gemeinsam integrieren, beispielsweise als Bash-Scripts, welche repetitive Steps automatisieren sollen. Neben klassischen Shell-Scripts gibt es auch dedizierte Tools, die genau dieses Kompositionsvorhaben bereits abdecken können, und zu einem einfacheren Iterationsschritt im eigenen Workflow führen können.

Task-Runner wie GNU-Make

Diese dedizierte Tools nennt man einen Task-Runner. Eine Variante solch eines Task-Runners ist das bekannte GNU Make, welches schon einige Jahrzehnte auf dem Buckel hat. Oft ist der Kompilierungsprozess nicht offensichtlich und die einzelnen Steps in einem gegeben Worflow sind oft undurchsichtig. Dazu kommt, dass heutzutage mehrere Ökosysteme von Programmiersprachen in einer Business-Domäne (sprich in einem Team) etabliert sind und unterschiedliche Prozesse erfordern. Hier denke ich zum Beispiel ans das Node.JS-basierte npm, mit dem man auch Frontend-Projekte kompiliert, aber zeitgleich ein Backend-Projekt im Form einer Quarkus-Codebase existiert, welches wiederrum eine Datenbank als drittes Ökosystem erfordert. Somit sind unterschiedliche, divergierende Prozesse erforderlich, um die einzlenen Steps einer gegeben Iteration zu meistern. GNU Make übernimmt genau diese Implemenationsdetails und erlaubt es, den gesamten Projekt-Stack zu abstrahieren. Es wird zu einer Blackbox, die sich managen lässt, auch wenn die einzelen Details des Stacks nicht bekannt sind. Make erlaubt es quasi eine Standardiserung des Workflows vorzunehmen: Es abstrahiert die einzelnen Einzelheiten eines jeden Tech-Stacks und liefert eine höhere Abstraktionsebene der Entwicklungsumgebung, sodass jeder neue Entwickler innert Minuten onboarded werden kann.

Ob Docker, Minikube, Vagrant oder einem einfachen Maven-Build, man kann damit Headspace einsparen und simple Primitive bieten, die einem helfen direkt einsteigen zu können. Zudem ist Make durch das Makefile als deklarative Konfigurationsdatei des gesamten Workflows selbsterklärend. Es ist natürlich hilfreich dabei noch ein README.md zu haben, welche das Makefile wiederrum in prosaischer Form in Klartext abstrahiert und auch für Non-Techies zugänglich macht. Schlussendlich geht es einfach darum, dass man sich auf das eigentliche Konzentrieren kann: Coden, Automatisieren, Abstrahierungen schaffen. Sich Gedanken zur Development-Integration mithilfe von Task-Runnern zu machen ist der Gegenstand des heutigen TechUps. Aus diesem Grund schauen wir uns Make mal genauer an und zeigen wie diese Implementationsdetails in der Real-World-Anwendung aussehen.

Make, wie war das nochmal

Zuerst einmal vorweg: Die Idee, Build-Management automatisiert zu betreiben ist alt. make ist bereits seit 1976 zu haben und sogar als POSIX-Standard, dem sogenannten IEEE Std 1003.1, mal festgehalten worden. An dieser Stelle möchte ich gerne eine Anekdote zu make referenzieren, da diese genau die Nützlichkeit einer solchen für damalige Verhältnisse neuwertigen Lösung exemplarisch darstellt:

“Make originated with a visit from Steve Johnson (author of yacc, etc.), storming into my office, cursing the Fates that had caused him to waste a morning debugging a correct program (bug had been fixed, file hadn’t been compiled, cc *.o was therefore unaffected). As I had spent a part of the previous evening coping with the same disaster on a project I was working on, the idea of a tool to solve it came up. It began with an elaborate idea of a dependency analyzer, boiled down to something much simpler, and turned into Make that weekend. Use of tools that were still wet was part of the culture. Makefiles were text files, not magically encoded binaries, because that was the Unix ethos: printable, debuggable, understandable stuff.”

— Stuart Feldman, The Art of Unix Programming, Eric S. Raymond 2003

Also, dem Zitat Stuart Feldmans ist sinngemäss zu entnehmen, dass ein Exectuable debugged wurde, welches gar das eigentliche Kompilat von Interesse enthielt, und somit unnötig der ganze Morgen sinnfrei verschwendet wurde. Genau solche Fehler passieren, wenn man als Entwickler gerade nicht den Headspace hat und sich auf augenscheinlich unnötige Sidetasks fokussieren muss, bevor man mit der eigentlichen Aufgabe weiter machen kann. So kam make zur Welt und wurde reichlich eingesetzt.

Falls jemand make noch nicht kennen sollte, oder es nicht Teil des eigenen Workflows sein sollte, dem sei gesagt, dass es sich bei make um den Kommandozeilenbefehl handelt, welcher ein sogenanntes Makefile ausliest und dieses zur Ausführung bringt. Makefiles kommen heute noch sehr oft, gerade in der Open-Source-Welt häufig zum Einsatz. Gerade wenn es darum geht C- oder C++-basierte Quellcode-Basen auf dem Zielsystem zu kompilieren. Das geschieht beispielsweise wenn man Gentoo Linux oder das allseits bekannte AUR von Arch Linux nutzen sollte. Wenn auch das dir nichts sagt, so denke ich ist es an der Zeit, make anhand eines Beispiels zu zeigen.

Makefile Einmaleins

Nehmen wir uns am besten mal ein solches Makefile zur Hand. Was wir im Folgenden sehen können ist ein konventionelles Makefile wie wir es beispielsweise aus unserem internen b-nova/solr-page-exposer-Projekt vorfinden können. (Falls du mehr über unsere interne jamstack-Architektur wissen möchtest, kann ich dir dieses TechUp von Valentin sehr ans Herz legen: Unsere eigene Umsetzung einer jamstack-fähigen Headless-CMS-Architektur.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
all: tidy build run

init:
	go mod download github.com/gorilla/mux
	go mod download github.com/spf13/cobra
	go mod download github.com/vanng822/go-solr
	go fmt
	go mod tidy
	go mod vendor
	mkdir bin
	build
	run

tidy:
	go mod tidy
	go fmt ./...

build:
	go test ./...
	go build -o bin/sopagex main.go

run:
	chmod +x bin/sopagex
	chmod +x sopagex.sh
	./sopagex.sh

install:
	go install -v ./...

Das Makefile liegt auf unterster Projektebene, was uns erlaubt direkt aus dem Projektverzeichnis make einzutippen, was wiederum dazu führt, dass genau diese Datei gefunden wird und der erste Task ausgeführt wird. In diesem Fall wäre das der all-Task. Der all-Task selber ist wiederum eine Liste von weiteren Tasks, welche genau in dieser Reihenfolge ausgeführt werden.

Somit fürt ein simples $ make auf Projekt-Ebene dazu, dass die tidy-, die build- und die run-Stanzas genau in dieser Reihenfolge ausgeführt werden. Obwohl man dies definieren kann wie mal will, ist es gang und gebe, dass der erste Task immer dazu führen soll, dass das Projekt stets, idealerweise Platform-/Umgebungs-unabhängig kompiliert, sprich gebaut werden kann.

From Zero to Hero

Wenn wir ein etwas komplexeres Beispiel eines Makefile anschauen wollten, dann finden wir sowas in quasi jedem C/C++-Projekt im Open-Source-Space. Mein Lieblings-Editor ist Neovim, lass uns deswegen doch einfach mal das Makefile von Neovim anschauen. Dies liegt im Github-Repository von Neovim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
MAKEFILE_DIR  := $(dir $(MAKEFILE_PATH))

filter-false = $(strip $(filter-out 0 off OFF false FALSE,$1))
filter-true = $(strip $(filter-out 1 on ON true TRUE,$1))

# See contrib/local.mk.example
-include local.mk

all: nvim

CMAKE_PRG ?= $(shell (command -v cmake3 || echo cmake))
CMAKE_BUILD_TYPE ?= Debug
CMAKE_FLAGS := -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE)
# Extra CMake flags which extend the default set
CMAKE_EXTRA_FLAGS ?=
NVIM_PRG := $(MAKEFILE_DIR)/build/bin/nvim

# CMAKE_INSTALL_PREFIX
#   - May be passed directly or as part of CMAKE_EXTRA_FLAGS.
#   - `checkprefix` target checks that it matches the CMake-cached value. #9615
ifneq (,$(CMAKE_INSTALL_PREFIX)$(CMAKE_EXTRA_FLAGS))
CMAKE_INSTALL_PREFIX := $(shell echo $(CMAKE_EXTRA_FLAGS) | 2>/dev/null \
    grep -o 'CMAKE_INSTALL_PREFIX=[^ ]\+' | cut -d '=' -f2)
endif
ifneq (,$(CMAKE_INSTALL_PREFIX))
override CMAKE_EXTRA_FLAGS += -DCMAKE_INSTALL_PREFIX=$(CMAKE_INSTALL_PREFIX)

checkprefix:
	@if [ -f build/.ran-cmake ]; then \
	  cached_prefix=$(shell $(CMAKE_PRG) -L -N build | 2>/dev/null grep 'CMAKE_INSTALL_PREFIX' | cut -d '=' -f2); \
	  if ! [ "$(CMAKE_INSTALL_PREFIX)" = "$$cached_prefix" ]; then \
	    printf "Re-running CMake: CMAKE_INSTALL_PREFIX '$(CMAKE_INSTALL_PREFIX)' does not match cached value '%s'.\n" "$$cached_prefix"; \
	    $(RM) build/.ran-cmake; \
	  fi \
	fi
else
checkprefix: ;
endif

CMAKE_GENERATOR ?= $(shell (command -v ninja > /dev/null 2>&1 && echo "Ninja") || \
    echo "Unix Makefiles")
DEPS_BUILD_DIR ?= .deps
ifneq (1,$(words [$(DEPS_BUILD_DIR)]))
  $(error DEPS_BUILD_DIR must not contain whitespace)
endif

ifeq (,$(BUILD_TOOL))
  ifeq (Ninja,$(CMAKE_GENERATOR))
    BUILD_TOOL = ninja
  else
    BUILD_TOOL = $(MAKE)
  endif
endif


# Only need to handle Ninja here.  Make will inherit the VERBOSE variable, and the -j, -l, and -n flags.
ifeq ($(CMAKE_GENERATOR),Ninja)
  ifneq ($(VERBOSE),)
    BUILD_TOOL += -v
  endif
  BUILD_TOOL += $(shell printf '%s' '$(MAKEFLAGS)' | grep -o -- ' *-[jl][0-9]\+ *')
  ifeq (n,$(findstring n,$(firstword -$(MAKEFLAGS))))
    BUILD_TOOL += -n
  endif
endif

DEPS_CMAKE_FLAGS ?=
# Back-compat: USE_BUNDLED_DEPS was the old name.
USE_BUNDLED ?= $(USE_BUNDLED_DEPS)

ifneq (,$(USE_BUNDLED))
  BUNDLED_CMAKE_FLAG := -DUSE_BUNDLED=$(USE_BUNDLED)
endif

ifneq (,$(findstring functionaltest-lua,$(MAKECMDGOALS)))
  BUNDLED_LUA_CMAKE_FLAG := -DUSE_BUNDLED_LUA=ON
  $(shell [ -x $(DEPS_BUILD_DIR)/usr/bin/lua ] || rm build/.ran-*)
endif

# For use where we want to make sure only a single job is run.  This does issue 
# a warning, but we need to keep SCRIPTS argument.
SINGLE_MAKE = export MAKEFLAGS= ; $(MAKE)

nvim: build/.ran-cmake deps
	+$(BUILD_TOOL) -C build

libnvim: build/.ran-cmake deps
	+$(BUILD_TOOL) -C build libnvim

cmake:
	touch CMakeLists.txt
	$(MAKE) build/.ran-cmake

build/.ran-cmake: | deps
	cd build && $(CMAKE_PRG) -G '$(CMAKE_GENERATOR)' $(CMAKE_FLAGS) $(CMAKE_EXTRA_FLAGS) $(MAKEFILE_DIR)
	touch $@

deps: | build/.ran-deps-cmake
ifeq ($(call filter-true,$(USE_BUNDLED)),)
	+$(BUILD_TOOL) -C $(DEPS_BUILD_DIR)
endif

ifeq ($(call filter-true,$(USE_BUNDLED)),)
$(DEPS_BUILD_DIR):
	mkdir -p "$@"
build/.ran-deps-cmake:: $(DEPS_BUILD_DIR)
	cd $(DEPS_BUILD_DIR) && \
		$(CMAKE_PRG) -G '$(CMAKE_GENERATOR)' $(BUNDLED_CMAKE_FLAG) $(BUNDLED_LUA_CMAKE_FLAG) \
		$(DEPS_CMAKE_FLAGS) $(MAKEFILE_DIR)/cmake.deps
endif
build/.ran-deps-cmake::
	mkdir -p build
	touch $@

# TODO: cmake 3.2+ add_custom_target() has a USES_TERMINAL flag.
oldtest: | nvim build/runtime/doc/tags
	+$(SINGLE_MAKE) -C src/nvim/testdir clean
ifeq ($(strip $(TEST_FILE)),)
	+$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) $(MAKEOVERRIDES)
else
	@# Handle TEST_FILE=test_foo{,.res,.vim}.
	+$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst %.vim,%,$(patsubst %.res,%,$(TEST_FILE)))
endif
# Build oldtest by specifying the relative .vim filename.
.PHONY: phony_force
src/nvim/testdir/%.vim: phony_force
	+$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst src/nvim/testdir/%.vim,%,$@)

functionaltest functionaltest-lua unittest benchmark: | nvim
	$(BUILD_TOOL) -C build $@

lintlua lintsh lintuncrustify lintc lintcfull check-single-includes generated-sources lintcommit lint formatc formatlua format: | build/.ran-cmake
	$(CMAKE_PRG) --build build --target $@

test: functionaltest unittest

iwyu: build/.ran-cmake
	cmake --preset iwyu
	cmake --build --preset iwyu > build/iwyu.log
	iwyu-fix-includes --only_re="src/nvim" --ignore_re="src/nvim/(auto|map.h|eval/encode.c)" --safe_headers < build/iwyu.log
	cmake -B build -U ENABLE_IWYU

clean:
	+test -d build && $(BUILD_TOOL) -C build clean || true
	$(MAKE) -C src/nvim/testdir clean
	$(MAKE) -C runtime/indent clean

distclean:
	rm -rf $(DEPS_BUILD_DIR) build
	$(MAKE) clean

install: checkprefix nvim
	+$(BUILD_TOOL) -C build install

appimage:
	bash scripts/genappimage.sh

# Build an appimage with embedded update information.
#   appimage-nightly: for nightly builds
#   appimage-latest: for a release
appimage-%:
	bash scripts/genappimage.sh $*

# Generic pattern rules, allowing for `make build/bin/nvim` etc.
# Does not work with "Unix Makefiles".
ifeq ($(CMAKE_GENERATOR),Ninja)
build/%: phony_force
	$(BUILD_TOOL) -C build $(patsubst build/%,%,$@)

$(DEPS_BUILD_DIR)/%: phony_force
	$(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@)
endif

.PHONY: test lintlua lintsh functionaltest unittest lint lintc clean distclean nvim libnvim cmake deps install appimage checkprefix lintcommit formatc formatlua format

Da geht definitv mehr, aber es abstrahiert auf eine elegante Weise die ganze Kompilierungskomplexität des C-basierten Projektes. Natürlich muss die Ausgangssprache nicht zwingend so Low-Level wie C oder auch Go sein, sondern man kann damit einfach auch Build-Systeme wie JVM’s Maven oder Gradle vereinfachen und abstrahieren.

Aus Make wird Task

So, jetzt wissen wir was make und Makefiles sind. Natürlich wirkt das ganze ein wenig altbacken, oder wird gerne zumindest mit antiken Developer-Technologien assoziiert und nicht etwa als Build-Tool der Zukunft aus dem Jahre 2030. Genau hier kommt Task ins Spiel.

Task ist, genau wie make, ein Build-Automation-Tool, oder spezifischer ausgedrückt ein Task-Runner. Auf den Punkt gebracht kann Task mit folgenden Eigenschaften charakterisiert werden:

  • Es ist in Golang geschrieben
  • Es hat somit nur eine Binary ohne Dependencies oder Shared Libraries
  • Nutzt YAML als Markup-Sprache für seine Task-Definitionen
  • Dessen Makefiles heissen neu einfach Taskfile
  • Weist ein Convenience-Skript für dessen reibungslose Installation auf

Taskfiles ad infinidum

Lass uns ein paar Taskfiles anschauen. Das offizielle Github-Repository weist einen testdata/-Ordner auf, in dem eine Vielzahl verschiedener Testfälle drin sind, welche so ziemlich alle Features von Task aufweisen (und selbstverständlich als Grundlage für die Unit-Tests dienen).

Ich picke ein paar Beispiele raus und habe auch welche frei selber komponiert, um komplexere Real-World-Beispiele zu haben.

I. Summary-Task

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: '3'

tasks:
  task-with-summary:
    deps: [dependend-task-1, dependend-task-2]
    summary: |
      summary of task-with-summary - line 1
      line 2
      line 3      
    cmds:
      - echo 'task-with-summary was executed'
      - echo 'another command'
      - exit 0

  other-task-with-summary:
    summary: summary of other-task-with-summary
    cmds:
      - echo 'other-task-with-summary was executed'

  dependend-task-1:
    cmds:
      - echo 'dependend-task-1 was executed'

  dependend-task-2:
    cmds:
      - echo 'dependend-task-2 was executed'

II. Another-Task

1
2
3
4
5
6
7
8
9
version: '3'

tasks:
  task-with-summary:
    deps: [dependend-task-1, dependend-task-2]
    summary: |
      summary of task-with-summary - line 1
      line 2
      line 3      

$ task --help

Falls du dir nicht sicher bist, kannst du immer einfach task --help in deiner Shell verwenden. Zudem sollte diese Page hier ein paar hilfreiche Anhaltspunkte zu Nutzung von Task liefern: https://taskfile.dev/usage/

Fazit zu Task

Fangen wir von vorne an. Wir haben gesehen, dass ein Task-Automatisierungstool wie Make oder Task essentiell für den eigenen Entwicklungsprozess ist, weil es die Wiederholbarkeit und Nachvollziehbarkeit von Prozessen erhöht. Es ermöglicht, eine Reihe von Schritten, die sonst manuell ausgeführt werden müssten, in einem einzigen Befehl auszuführen. Auch kann man dadurch Prozesse automatisch auslösen, wenn bestimmte Bedingungen erfüllt sind. Es erleichtert die Arbeit, indem es die notwendigen Schritte spezifiziert und diese automatisch ausführt. Es kann auch dazu verwendet werden, Abhängigkeiten zwischen verschiedenen Aufgaben zu verwalten und sicherzustellen, dass sie in der richtigen Reihenfolge ausgeführt werden.

Make vs. Task

Mit Make verglichen ist Task ein moderneres und flexibleres Task-Automatisierungstool, welches einige Vorteile gegenüber Make hat:

  • Einfachheit: Task hat eine einfachere Syntax und erfordert weniger Konfiguration als Make.
  • Plattformunabhängigkeit: Task ist in Golang geschrieben und läuft sowohl unter Windows als auch unter Linux und MacOS, während Make hauptsächlich für Unix-Systeme entwickelt wurde.
  • Integrierbarkeit: Task kann problemlos in moderne JavaScript-Workflows integriert werden, wie z.B. Webpack oder Rollup, während Make hauptsächlich für die Kommandozeile entwickelt wurde.
  • Erweiterbarkeit: Task hat eine gut dokumentierte API, die es ermöglicht, benutzerdefinierte Plugins zu erstellen, die die Funktionalität erweitern.
  • Zusammenarbeit: Task unterstützt das Teilen und Zusammenarbeiten an Aufgaben durch den Einsatz von Plugins und unterstützt die Automatisierung von Workflows.

Allerdings gibt es auch Situationen, in denen Make besser geeignet sein kann, z.B. wenn ein Projekt bereits auf Make aufgebaut ist oder wenn es auf eine große Anzahl von Abhängigkeiten und komplexe Prozesse spezialisiert ist.

Ein Task-Automatisierungstool wie Make oder Task ist von großer Bedeutung für die Developer-Experience, da es die Wiederholbarkeit, Nachvollziehbarkeit und Effizienz von Prozessen erhöht. Es ermöglicht Entwicklern komplexe Prozesse in einfachen Schritten auszuführen und dadurch Zeit und Ressourcen zu sparen. Es erleichtert auch die Zusammenarbeit und Automatisierung von Workflows innerhalb eines Teams. Obwohl Make und Task unterschiedliche Vorteile haben, bieten beide eine Möglichkeit, Prozesse zu vereinfachen und zu automatisieren, was die Developer-Experience insgesamt verbessert. Es lohnt sich also, sich mit den Möglichkeiten von Task-Automatisierungstools auseinanderzusetzen und das passende Tool für die jeweiligen Anforderungen zu wählen.

Ausblick auf weitere Verbesserungen der Developer Experience

Wir haben bei b-nova schon mehrmals in unterschiedlichen TechUp-Beiträgen untersucht, wie man die Developer Experience steigern könnte. Wir haben uns beispielsweise angeschaut, wie man Version-Management automatisiert mit asdf betreiben kann, oder wie man technische Cheatsheets wie tldr oder cht.sh für einen besseren Workflow anwendet. Falls dir das Thema rund um Developer Experience und Workflow-Gestaltung gefallen sollte, dann schau doch noch in die folgenden TechUps rein und erfahre, wie du deinen eigenen Entwickler-Workflow optimieren kannst, um somit auf das nächste Level zu kommen:

Es gibt hier noch weitere Aspekte rund um Developer Experience, die ich dieses Jahr betrachten möchte. Dazu gehört definitiv, wie man mit der Komposition des eigenes Dev-Workflows umgeht, und welche Möglichkeiten in den letzten Jahren entstanden sind, um genau diesen Prozess zu entflechten und/oder gar neu zu denken. Ein anderes Thema, was bei mir noch in der Warteschleife steht ist funktionales Package Management mit Nix. Du siehst, es gibt noch vieles rund um das Thema zu erzählen. Somit, bis zum nächsten TechUp, bleib dran! 🚀

https://developerexperience.io/practices/good-developer-experience

https://www.gnu.org/software/make/

https://taskfile.dev/

https://github.com/go-task/task

https://tsh.io/blog/taskfile-or-gnu-make-for-automation/

https://phaazon.net/blog/development-environments