JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

109
Wissenstransfer 2.0: Liest du noch, oder programmierst du schon? The knowledge transfer company

Transcript of JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Page 1: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Wissenstransfer 2.0: Liest du noch, oder programmierst du schon?

The knowledge transfer company

Page 2: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

• Project Lead of Eclipse Code Recommenders • Eclipse Committer since 2010 • Plug-in Developer for 10 years (Eclipse 2.1) • Member of the Eclipse Architecture Council • Co-Lead of Java User Group Darmstadt • Speaker at JUGs, EclipseCon, JAX, JavaOne… • PhD in Computer Science • CEO of Codetrails

!• Passion to improve developers day-to-day work

with intelligent and practical tools - mostly using Data Mining on Big (Software Engineering) Data.

About Me

2

@MarcelBruch +MarcelBruch

Page 3: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Codetrails at a Glance

• The company behind Eclipse Code Recommenders

• Eclipse RCP Experts & Consultants

• Software Tool Developers

• Data Mining Specialists

• Research Spin-off Darmstadt University of Technology

3

supported by

Page 4: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

About Eclipse Code Recommenders

4

2006 2009 2011

2012 2013 2014

Research begins

CR published CR goes

Eclipse

CR 1.0 Part of IDE CR 2.0 CR 2.1

Page 5: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Awards & Traction

5

2012

• >3.000.000 downloads

Top 20

• Talks at 50+ Democamps, Eclipse Days, and Java User Groups all over Europe.

• Talks at 10+ intl. and local conferences.

• articles every 4 months, • lots of general coverage

Page 6: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

PROBLEM

Page 7: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem #1: Knowledge Drain

7

Page 8: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem #1: Knowledge Drain

8

Page 9: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem #1: Knowledge Drain

9

Software

Page 10: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem #2: Continuous Learning

10

Page 11: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

A typical developer day

11

Comprehend Task 3 %

Handling Interruptions 20 %

Navigate Code 14 %

Study Code 19 %

Searching Web 12 %

Reading Docs 6 %

Write Code 27 %

51% of their time, developers invest in searching and understanding code

Page 12: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

How we search

12

Page 13: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What developers look for

13Question + Answer + Code Example = ✓

Page 14: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

A typical developer day

14

Comprehend Task 3 %

Handling Interruptions 20 %

Navigate Code 14 %

Study Code 19 %

Searching Web 12 %

Reading Docs 6 %

Write Code 27 %

Even when writing code we search…

Page 15: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

If we don’t google…

15

Page 16: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

1 of 387 ?

Page 17: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

17

Code Completion

Frequency : 100 completions / day

Duration (⌀) : 27 seconds / completion

Sum : 45 minutes / day

9% of their time developers spent searching in their code completion

Page 18: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem

18

Comprehend Task 3 %

Handling Interruptions 20 %

Searching Code 12 %

Navigate Code 14 %

Reading Code 19 %

Reading Docs 6 %

Edit Code 27 %

Page 19: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Problem

19

Comprehend Task 3 %

Handling Interruptions 20 %

Searching Code 12 %

Navigate Code 14 %

Reading Code 19 %

Reading Docs 6 %

Edit Code 27 %

• 51% of their time, developers spent searching the web and existing code for help

• In addition, even when programming, they spent 9% searching in their code completion

• In total, 60% is about finding the right information to solve a programming task

Page 20: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

SOLUTION„Leveraging the power of the crowds“

Page 21: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Behind every answered question…

21

Page 22: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

… there is some code

22

Page 23: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Use the code

23

Page 24: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

INTELLIGENT CODE COMPLETION„Other developers frequently bought used the following methods…“

Page 25: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

@Override!protected Control createDialogArea(final Composite parent) {! Composite container = (Composite) super.createDialogArea(parent);! ! swtTextWidget = new Text(container, SWT.BORDER);! swtTextWidget.|! return container;!}

Code Completion…

What does the developer need, i.e., which methods should the code completion present to the user?

25

Page 26: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

All 164 Methods of Text ?

Page 27: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Intelligent Code Completion

What your team really needs to know…

27

Page 28: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Snippet Completion (1)

28

Page 29: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

HOW IT WORKS„Finding the hidden gems in your code“

Page 30: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

class Example extends Page {! Text t;!! @Override! void createContents() {! t = new Text();! t.setText(..);! ...! }! ...!}

in:Page.performOk()

Text.<init>()

Text.getText()

Text.setFont()

...

Text.setText()

… … … … … … …

0 0 0 …

30

1 1 1

From code to models...

in:Page.createContents()

Page 31: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

31

class Example extends Page {! Text t;! ...!! @Override! void performOk() {! t.getText();! ...! }!!}

in:Page.performOk()

Text.<init>()

Text.getText()

Text.setFont()

...

Text.setText()

1 0 1 1

1 1

0 0

0 0 0 0… … … … … …

……

From code to models...

in:Page.createContents()

Page 32: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

32

class MyPage extends Page {!! Text t;! @Override! void createContents() {! t = new Text();! t.|<^space>! }! ...!}

1 …1 ?? ?0

From models to recommendations...

in:Page.performOk()

Text.<init>()

Text.getText()

Text.setFont()

...

Text.setText()

in:Page.createContents()

Page 33: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

33

1 …1 ?? ?0

in:Page.performOk()

Text.<init>()

Text.getText()

Text.setFont()

...

Text.setText()

01 10 0 …1 10 0 0 0

… … … … … ………

1

0 1 1 1 0 …101 00 0 …1

⅔ ⅓ 0

From models to recommendations...

in:Page.createContents()

Page 34: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

CALL-CHAIN COMPLETION“Welcome to the jungle...”

Page 35: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

public class MyView extends ViewPart {! ! public void updateMessage(final String newMessage) {!! // How do I get an instance of IStatusLineManager?! final IStatusLineManager manager = |!! }

How to obtain an instance of...

There is no trivial solution. Again, what should code completion return?

35

Page 36: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What we need...

36

ViewPart

IViewSite

IAc-onBars

IStatusLineManager

getStatusLineManager()

getAc-onbars()

getViewSite()public void updateMessage(final String newMessage) {!! // How do I get an instance of ! ! final IStatusLineManager manager = ! this! .getViewSite()! .getActionBars()! .getStatusLineManager();!! manager.setMessage(newMessage);!}

Page 37: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

SUBWORDS COMPLETION“BecausePureCamelCaseMatching****s!”

Page 38: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Use speaking names...?

38

Page 39: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

When letters become just guidelines...

39I Like?Source: http://vimeo.com/19369928

Page 40: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

EXTENDING API DOCUMENTATION“Developers who bought overwrote this method typically also overwrote...”

Page 41: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

In good documentation we trust...

41

good

/**! * Rollsback the transaction if any and clears different lists to! * start with an empty resource again.! * Note that the super.doUnload is not called because that clears! * the list resulting in all kinds of undesirable inverseremoves.! */! @Override! protected void doUnload() {! super.doUnload();! }!!

No? Are you sure?

Page 42: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Generating high-quality API documentation

42

/**! * The number of questions.! */! private int numberOfQuestions;!!! /**! * Sets the number of questions.! * ! * @param numberOfQuestions the number of questions! * @throws IllegalArgumentException the illegal argument exception! */! public void setNumberOfQuestions(int numberOfQuestions) !

throws IllegalArgumentException {! if (numberOfQuestions < 0) {! throw new IllegalArgumentException("numberOfQuestions < 0");! }! this.numberOfQuestions = numberOfQuestions;! }

Page 43: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What people say about JAutodoc...

43

User: Anonymous Rating: 9 2009-08-02 11:32:37

Wow exactly what I needed!

User: Anonymous Rating: 9 2009-02-13 19:58:32

Thank you... this plugin rocks!

User: Anonymous Rating: 9 2009-02-13 19:58:32

Works perfectly. Smarter than I expected!

WTH? What did you expect?!

Page 44: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

How Recommenders is coming to rescue...

44

Page 45: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What documentation would you expect when subclassing Dialog?

45

package org.eclipse.recommenders.examples.demo;!!import org.eclipse.jface.*;!!public class MyDialog extends Dialog {!! protected MyDialog(final IShellProvider parentShell) {! super(parentShell);! } !}!!

Page 46: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What Javadoc gives you...

46

A dialog is a specialized window used for narrow-focused communication with the user. !Dialogs are usually modal. Consequently, it is generally bad practice to open a dialog without a parent. A modal dialog without a parent is not prevented from disappearing behind the application's other windows, making it very confusing for the user. !If there is more than one modal dialog is open the second one should be parented off of the shell of the first one otherwise it is possible that the OS will cus to the first dialog potentially blocking the UI.

But what are the hot-spots of Dialog?

org.eclipse.jface.dialogs.Dialog

Page 47: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What code completion offers...

47

Which of the 56 methods should we override?

Page 48: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Recommending method overrides...

48

! public class MyDialog extends Dialog {!!

Page 49: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Recommending self-calls

49

public class MyWizard extends Wizard {!!! @Override! public void addPages() {! ! };

Page 50: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Mining for subclassing patterns

50

!public class MyViewerSorter extends ViewerComparator {

Page 51: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Mined example code snippets…

51

! public class MyDialog extends Dialog {! ! @Override! protected Control createDialogArea(Composite parent) {!!

Page 52: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

SNIPMATCH„Stop searching Stackoverflow for code snippets“

Page 53: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Why do you use code templates?

53

Page 54: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Safe time typing

54

<ctrl+space>

<ctrl+space>

{<ctrl+space>

<ctrl+space>

}

Page 55: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Learning from snippets

55

Page 56: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Eclipse Java Templates

56

42x Java Templates. 35x SWT Templates.

Page 57: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Learning from snippets

57

Page 58: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

The rise of code snippet repositories

58

and many more...

Page 59: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Snipmatch

59Recommenders

2.1

Page 60: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

AND IF THERE IS NO CODE TO ANALYZE?„From code analysis to crowd sourcing“

Page 61: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Being for Software DevelopersHow 39 million click events make your IDE a smarter place

Page 62: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

62

Page 63: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

63

Page 64: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

The amazon success factors

64

Page 65: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

65

How about applying the very same principles to software development?

Page 66: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

So, instead of using the web browser…

66

Page 67: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0
Page 68: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

68

!!!

Disclaimer !

Data sharing is involved!

!

Not Eclipse anymore!

Page 69: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Crowdsourced Code CompletionDevelopers that bought used a StringBuilder, typically…

69

Page 70: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

A fairly trivial example…

70

Page 71: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Instead saying…

71

Page 72: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What if we‘d just count calls...?

72Codetrails Connect Community Edition, Install from http://www.codetrails.com/connect

Page 73: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

!void ctor() {! List l = new A…}

Crowdsourced Constructor Completion

73

What is the mostly likely completion a developer looks for in this particular situation ?

Page 74: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

74

1 out of 302

Page 75: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

!void ctor() {! List l = new A…}

Use (costly) subtype checks

75

Codetrails Completion Tweaks for Eclipse, Install from http://download.codetrails.com/updates/connect/

Page 76: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Crowdsourcing constructor completions...?

76

It learns from you. From the very 1st moment.

Codetrails Connect Community Edition, Install from http://download.codetrails.com/updates/connect/

Page 77: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

public class MyDialog extends Dialog {! |!!

Crowdsourcing override completions...?

77

Page 78: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

!void quickfix() {! List l = new A}

How about fixing quick-fix?

78Not fixed yet. But could be easily - when you start sharing your knowledge…

Page 79: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

39.000.000 clicks for a smarter IDE

79Read more (October 2013) http://www.codetrails.com/blog/powered-33-million-code-completions-codetrails-connect-12

Method Overrides 1.579.303

Constructor Calls 8.915.540

Method Calls 38.254.438

48.749.281

Bootstrapped with30++ GB Java Bytecode. Powered by Maven Central and EclipseSource Yoxos.

Page 80: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

56.424 crowdsourced completion events

80

org.eclipse.* 11 %org.apache.*

4 %com.google.*

9 %

com.vaadin.* 5 %

com.codetrails.* 3 %

others 14 %

javax.* 3 %

java.[n]io.* 6 %

java.util.* 25 %

java.lang.* 21 %

From December 22 to March 12

Page 81: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Trend: Events sent by the community

81

Visit the Codetrails Connect live dashboard: http://download.codetrails.com/connect/dashboard/

Page 82: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

How well does it work?

82

What do you expect?

Page 83: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

How well does it work?

83

Num

ber o

f pro

posa

ls m

ade

1

2

3

4

5

6

7

Position in code completion window-1 0 1 2 3 4 5 6

993

475

304

214

189

123

1.094

833

327 238

197 114 87

145 56 40 38

96 61 23 22 21

77 52 19 21 20 10

963 591 373 243 285 151 157

HitMiss

not found

Page 84: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Successfully concluded sessions

84

Codetrails 78,8

JDT 67,9

JDT standalone vs. Codetrails

Developers find what they need 11% more often with smart code completion. Number of documentation lookups saved in 3 months: 3.000!

Page 85: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What get’s shared?

85

Page 86: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What is shared?

86

{!„type“: „StringBuilder“,!„completion“: „append(String)“!

}

Page 87: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Sharing Preferences

87

Page 88: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Still scared of crowdsourcing?

88

Do you also suffer from Anatidaephobia?„Big Brother is watching you“ http://anniemachon.ch/annie_machon/2013/11/cryptofestival-london-30th-november.html

Page 89: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

89

ANATIDAEPHOBIA IS THE FEAR THAT SOMEWHERE IN THE WORLD, THERE IS A DUCK WATCHING YOU.

Page 90: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

90

https://vaadin.com/blog/-/blogs/crowdsourcing-vaadin-with-codetrails-connect

Page 91: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

91

Snippets going social

5 0

Page 92: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Enriching Javadoc

92Eclipse Code Recommenders Livedoc 0.8 Incubator Install from http://eclipse.org/recommenders/incubators

Page 93: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Javadocs with mined snippets

93http://download.codetrails.com/livedoc/published/latest/org/eclipse/jface/org.eclipse.jface/3.8.102/

Page 94: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

94

Breaking News:!Words carry meaning!As leading scientists found out, words....

Rethinking Recommendations

for Code Completion

Page 95: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

A simple observation

95

You wouldn‘t expect a call to #dispose() in #createSomething(), right?

Page 96: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What you expect is...

96

Page 97: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

How many words do you have for ‟create” ?

97

Page 98: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

So, it works for #createSomething too!

98

Page 99: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Almost no difference!

99

Prec

isio

n

0

25

50

75

100

Recall

0 10 20 30 40 50 60 70 80 90 100

ClassicVerbs

Interpolated Precision Recall Curves over 136.761 samples on JRE

Page 100: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

What get‘s shared?

100

{!„method“: „create“!„type“: „Composite“,!„completion“: „setLayout(...)“!

}

Page 101: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Let‘s back up here for a second!

101

Page 102: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

We get (almost) language agnostic!

102

Page 103: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Read More

103

Page 104: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

Installation

104

Page 105: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

WRAP UP„Closing notes“

Page 106: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

All that only happens if you share…

106

Page 107: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

107

Yes, there is a duck somewhere.

Page 108: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

108But it probably doesn’t care what you are doing.

Page 109: JUG Münster 2014 - Code Recommenders & Codetrails - Wissenstransfer 2.0

THANK YOU

@MarcelBruch +MarcelBruch